calagator 0.0.1.pre1

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 (384) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +75 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/images/confused-alligator-sm.png +0 -0
  6. data/app/assets/images/disk.png +0 -0
  7. data/app/assets/images/edit.png +0 -0
  8. data/app/assets/images/external_sites/epdx.png +0 -0
  9. data/app/assets/images/external_sites/external.gif +0 -0
  10. data/app/assets/images/external_sites/facebook.png +0 -0
  11. data/app/assets/images/external_sites/foursquare.png +0 -0
  12. data/app/assets/images/external_sites/gowalla.png +0 -0
  13. data/app/assets/images/external_sites/lanyrd.png +0 -0
  14. data/app/assets/images/external_sites/meetup.png +0 -0
  15. data/app/assets/images/external_sites/plancast.png +0 -0
  16. data/app/assets/images/external_sites/shizzow.png +0 -0
  17. data/app/assets/images/external_sites/upcoming.png +0 -0
  18. data/app/assets/images/external_sites/yelp.png +0 -0
  19. data/app/assets/images/feed.png +0 -0
  20. data/app/assets/images/heart.png +0 -0
  21. data/app/assets/images/icon_ical.gif +0 -0
  22. data/app/assets/images/information.png +0 -0
  23. data/app/assets/images/nav_marker.gif +0 -0
  24. data/app/assets/images/nav_marker.png +0 -0
  25. data/app/assets/images/plus.png +0 -0
  26. data/app/assets/images/redx.png +0 -0
  27. data/app/assets/images/site-icon.png +0 -0
  28. data/app/assets/images/spinner.gif +0 -0
  29. data/app/assets/images/star.png +0 -0
  30. data/app/assets/images/subnav_marker.gif +0 -0
  31. data/app/assets/images/subnav_marker.png +0 -0
  32. data/app/assets/images/tag_blue.png +0 -0
  33. data/app/assets/images/tag_icons/angular.png +0 -0
  34. data/app/assets/images/tag_icons/beer.png +0 -0
  35. data/app/assets/images/tag_icons/bitcoin.png +0 -0
  36. data/app/assets/images/tag_icons/free.png +0 -0
  37. data/app/assets/images/tag_icons/golang.png +0 -0
  38. data/app/assets/images/tag_icons/html.png +0 -0
  39. data/app/assets/images/tag_icons/javascript.png +0 -0
  40. data/app/assets/images/tag_icons/linux.png +0 -0
  41. data/app/assets/images/tag_icons/php.png +0 -0
  42. data/app/assets/images/tag_icons/pizza.png +0 -0
  43. data/app/assets/images/tag_icons/python.png +0 -0
  44. data/app/assets/images/tag_icons/rails.png +0 -0
  45. data/app/assets/images/tag_icons/ruby.png +0 -0
  46. data/app/assets/images/tag_icons/sass.png +0 -0
  47. data/app/assets/images/tag_icons/swift.png +0 -0
  48. data/app/assets/images/transmit_blue.png +0 -0
  49. data/app/assets/images/weekday_background.gif +0 -0
  50. data/app/assets/javascripts/calagator.js +18 -0
  51. data/app/assets/javascripts/calagator/forms.js +60 -0
  52. data/app/assets/stylesheets/calagator.scss +8 -0
  53. data/app/assets/stylesheets/calagator/common.scss +14 -0
  54. data/app/assets/stylesheets/calagator/datepicker.scss +177 -0
  55. data/app/assets/stylesheets/calagator/errors.css +110 -0
  56. data/app/assets/stylesheets/calagator/forms.scss +4 -0
  57. data/app/assets/stylesheets/calagator/reset.css +40 -0
  58. data/app/assets/stylesheets/calagator/theme.css +0 -0
  59. data/app/controllers/calagator/admin_controller.rb +27 -0
  60. data/app/controllers/calagator/application_controller.rb +82 -0
  61. data/app/controllers/calagator/events_controller.rb +171 -0
  62. data/app/controllers/calagator/site_controller.rb +35 -0
  63. data/app/controllers/calagator/sources_controller.rb +94 -0
  64. data/app/controllers/calagator/venues_controller.rb +133 -0
  65. data/app/controllers/calagator/versions_controller.rb +20 -0
  66. data/app/helpers/calagator/application_helper.rb +128 -0
  67. data/app/helpers/calagator/events_helper.rb +184 -0
  68. data/app/helpers/calagator/google_event_export_helper.rb +67 -0
  69. data/app/helpers/calagator/mapping_helper.rb +103 -0
  70. data/app/helpers/calagator/sources_helper.rb +10 -0
  71. data/app/helpers/calagator/tags_helper.rb +42 -0
  72. data/app/helpers/calagator/time_range_helper.rb +166 -0
  73. data/app/models/calagator/event.rb +246 -0
  74. data/app/models/calagator/event/cloner.rb +39 -0
  75. data/app/models/calagator/event/ical_renderer.rb +155 -0
  76. data/app/models/calagator/event/overview.rb +45 -0
  77. data/app/models/calagator/event/saver.rb +58 -0
  78. data/app/models/calagator/event/search.rb +72 -0
  79. data/app/models/calagator/event/search_engine.rb +28 -0
  80. data/app/models/calagator/event/search_engine/apache_sunspot.rb +106 -0
  81. data/app/models/calagator/event/search_engine/sql.rb +107 -0
  82. data/app/models/calagator/source.rb +115 -0
  83. data/app/models/calagator/source/importer.rb +46 -0
  84. data/app/models/calagator/source/parser.rb +128 -0
  85. data/app/models/calagator/source/parser/facebook.rb +67 -0
  86. data/app/models/calagator/source/parser/hcal.rb +108 -0
  87. data/app/models/calagator/source/parser/http_authentication_required_error.rb +6 -0
  88. data/app/models/calagator/source/parser/ical.rb +186 -0
  89. data/app/models/calagator/source/parser/meetup.rb +72 -0
  90. data/app/models/calagator/source/parser/not_found.rb +9 -0
  91. data/app/models/calagator/source/parser/plancast.rb +59 -0
  92. data/app/models/calagator/venue.rb +161 -0
  93. data/app/models/calagator/venue/geocoder.rb +51 -0
  94. data/app/models/calagator/venue/search.rb +63 -0
  95. data/app/models/calagator/venue/search_engine.rb +24 -0
  96. data/app/models/calagator/venue/search_engine/apache_sunspot.rb +85 -0
  97. data/app/models/calagator/venue/search_engine/sql.rb +68 -0
  98. data/app/models/event/search_engine/base.rb +0 -0
  99. data/app/observers/calagator/cache_observer.rb +37 -0
  100. data/app/views/calagator/admin/events.html.erb +28 -0
  101. data/app/views/calagator/admin/index.html.erb +8 -0
  102. data/app/views/calagator/events/_feed_item.html.erb +62 -0
  103. data/app/views/calagator/events/_form.html.erb +63 -0
  104. data/app/views/calagator/events/_gcal_reminder.html.erb +1 -0
  105. data/app/views/calagator/events/_hcal.html.erb +41 -0
  106. data/app/views/calagator/events/_item.html.erb +120 -0
  107. data/app/views/calagator/events/_list.html.erb +30 -0
  108. data/app/views/calagator/events/_list_item.html.erb +37 -0
  109. data/app/views/calagator/events/_search_section.html.erb +15 -0
  110. data/app/views/calagator/events/_subnav.html.erb +13 -0
  111. data/app/views/calagator/events/_table.html.erb +74 -0
  112. data/app/views/calagator/events/duplicates.html.erb +58 -0
  113. data/app/views/calagator/events/edit.html.erb +3 -0
  114. data/app/views/calagator/events/index.atom.builder +32 -0
  115. data/app/views/calagator/events/index.html.erb +51 -0
  116. data/app/views/calagator/events/index.kml.erb +20 -0
  117. data/app/views/calagator/events/new.html.erb +7 -0
  118. data/app/views/calagator/events/search.html.erb +25 -0
  119. data/app/views/calagator/events/show.html.erb +81 -0
  120. data/app/views/calagator/site/_appropriateness_message.html.erb +15 -0
  121. data/app/views/calagator/site/_description.html.erb +3 -0
  122. data/app/views/calagator/site/about.html.erb +7 -0
  123. data/app/views/calagator/site/defunct.html.erb +15 -0
  124. data/app/views/calagator/site/export.html.erb +4 -0
  125. data/app/views/calagator/site/index.html.erb +40 -0
  126. data/app/views/calagator/site/opensearch.xml.builder +8 -0
  127. data/app/views/calagator/sources/_subnav.html.erb +0 -0
  128. data/app/views/calagator/sources/edit.html.erb +12 -0
  129. data/app/views/calagator/sources/import.html.erb +10 -0
  130. data/app/views/calagator/sources/index.html.erb +22 -0
  131. data/app/views/calagator/sources/new.html.erb +32 -0
  132. data/app/views/calagator/sources/show.html.erb +16 -0
  133. data/app/views/calagator/venues/_form.html.erb +57 -0
  134. data/app/views/calagator/venues/_subnav.html.erb +10 -0
  135. data/app/views/calagator/venues/duplicates.html.erb +65 -0
  136. data/app/views/calagator/venues/edit.html.erb +3 -0
  137. data/app/views/calagator/venues/index.html.erb +93 -0
  138. data/app/views/calagator/venues/index.kml.erb +15 -0
  139. data/app/views/calagator/venues/map.html.erb +4 -0
  140. data/app/views/calagator/venues/new.html.erb +5 -0
  141. data/app/views/calagator/venues/show.html.erb +113 -0
  142. data/app/views/calagator/versions/_chooser.html.erb +28 -0
  143. data/app/views/calagator/versions/_edit_with_chooser.html.erb +16 -0
  144. data/app/views/layouts/calagator/application.html.erb +110 -0
  145. data/config/deploy/local.rb +37 -0
  146. data/config/deploy/lucca.rb +33 -0
  147. data/config/initializers/dates.rb +7 -0
  148. data/config/initializers/formtastic.rb +82 -0
  149. data/config/initializers/geokit.rb +74 -0
  150. data/config/initializers/ics_renderer.rb +6 -0
  151. data/config/initializers/load_tag_model_extensions.rb +3 -0
  152. data/config/initializers/mime_types.rb +9 -0
  153. data/config/initializers/observers.rb +1 -0
  154. data/config/initializers/search_engine.rb +4 -0
  155. data/config/initializers/set_default_url_host.rb +5 -0
  156. data/config/initializers/time_get_zone.rb +8 -0
  157. data/config/locales/en.yml +5 -0
  158. data/config/routes.rb +55 -0
  159. data/config/secrets.yml.blag +75 -0
  160. data/config/sunspot.yml +23 -0
  161. data/db/development.sqlite3 +0 -0
  162. data/db/development.sqlite3.bak +0 -0
  163. data/db/development.sqlite3.old +0 -0
  164. data/db/development~20111112@110950.sqlite3 +0 -0
  165. data/db/migrate/001_create_events.rb +17 -0
  166. data/db/migrate/002_create_venues.rb +17 -0
  167. data/db/migrate/003_create_sources.rb +16 -0
  168. data/db/migrate/004_add_detailed_fields_to_venue.rb +19 -0
  169. data/db/migrate/005_add_end_time_to_events.rb +9 -0
  170. data/db/migrate/006_add_source_id_to_events.rb +9 -0
  171. data/db/migrate/008_add_source_id_to_venues.rb +10 -0
  172. data/db/migrate/009_add_duplicate_of_column_to_venues.rb +9 -0
  173. data/db/migrate/010_add_duplicate_of_column_to_events.rb +9 -0
  174. data/db/migrate/011_change_lat_long_type.rb +12 -0
  175. data/db/migrate/012_add_source_reimport.rb +9 -0
  176. data/db/migrate/013_change_end_time_to_duration.rb +11 -0
  177. data/db/migrate/014_remove_format_type_from_source.rb +9 -0
  178. data/db/migrate/015_create_updates.rb +15 -0
  179. data/db/migrate/016_remove_next_update_from_source.rb +9 -0
  180. data/db/migrate/20080705163959_change_duration_to_end_time.rb +11 -0
  181. data/db/migrate/20080705164959_create_tags_and_taggings.rb +28 -0
  182. data/db/migrate/20081011181519_create_versioned_events.rb +25 -0
  183. data/db/migrate/20081011193124_create_versioned_venues.rb +32 -0
  184. data/db/migrate/20081115190515_add_rrule_to_events.rb +15 -0
  185. data/db/migrate/20090912082129_create_versions.rb +18 -0
  186. data/db/migrate/20110219205156_add_closed_flag_to_venues.rb +9 -0
  187. data/db/migrate/20110220001008_add_wifi_flag_to_venues.rb +9 -0
  188. data/db/migrate/20110220011427_add_access_notes_to_venues.rb +9 -0
  189. data/db/migrate/20110220031117_add_events_count_to_venues.rb +8 -0
  190. data/db/migrate/20110604174521_add_venue_details_to_events.rb +9 -0
  191. data/db/migrate/20110717231316_acts_as_taggable_on_migration.rb +50 -0
  192. data/db/migrate/20120709092821_cleanup.rb +14 -0
  193. data/db/migrate/20120831234448_specify_venues_latitude_and_longitude_precision.rb +11 -0
  194. data/db/migrate/20150206085809_remove_updates.rb +13 -0
  195. data/db/migrate/20150207231355_add_locked_status_to_events.rb +5 -0
  196. data/db/production.sqlite3 +0 -0
  197. data/db/schema.rb +102 -0
  198. data/db/seeds.rb +16 -0
  199. data/db/test.sqlite3 +0 -0
  200. data/db/test2.sqlite3 +0 -0
  201. data/lib/calagator.rb +30 -0
  202. data/lib/calagator/blacklist_validator.rb +69 -0
  203. data/lib/calagator/decode_html_entities_hack.rb +33 -0
  204. data/lib/calagator/duplicate_checking.rb +133 -0
  205. data/lib/calagator/duplicate_checking/controller_actions.rb +38 -0
  206. data/lib/calagator/duplicate_checking/duplicate_finder.rb +83 -0
  207. data/lib/calagator/duplicate_checking/duplicate_squasher.rb +74 -0
  208. data/lib/calagator/engine.rb +30 -0
  209. data/lib/calagator/strip_whitespace.rb +19 -0
  210. data/lib/calagator/tag_model_extensions.rb +104 -0
  211. data/lib/calagator/url_prefixer.rb +9 -0
  212. data/lib/calagator/version.rb +3 -0
  213. data/lib/generators/calagator/install_generator.rb +39 -0
  214. data/lib/generators/calagator/templates/config/calagator.rb +26 -0
  215. data/lib/generators/calagator/templates/config/secrets.yml.sample +83 -0
  216. data/lib/secrets_reader.rb +76 -0
  217. data/lib/tasks/spec_db.rake +53 -0
  218. data/lib/tasks/sunspot_reindex_calagator.rake +6 -0
  219. data/lib/tasks/sunspot_solr_restart_enhancements.rake +19 -0
  220. data/lib/tasks/update_counter_caches.rake +13 -0
  221. data/lib/templates/erb/scaffold/_form.html.erb +11 -0
  222. data/lib/theme_reader.rb +17 -0
  223. data/lib/wait_for_solr.rb +25 -0
  224. data/spec/controllers/calagator/application_controller_spec.rb +47 -0
  225. data/spec/controllers/calagator/events_controller_spec.rb +794 -0
  226. data/spec/controllers/calagator/site_controller_spec.rb +59 -0
  227. data/spec/controllers/calagator/sources_controller_spec.rb +439 -0
  228. data/spec/controllers/calagator/venues_controller_spec.rb +319 -0
  229. data/spec/controllers/calagator/versions_controller_spec.rb +82 -0
  230. data/spec/controllers/squash_many_duplicates_examples.rb +49 -0
  231. data/spec/dummy/Gemfile +39 -0
  232. data/spec/dummy/Gemfile.lock +195 -0
  233. data/spec/dummy/README.rdoc +261 -0
  234. data/spec/dummy/Rakefile +7 -0
  235. data/spec/dummy/app/assets/images/rails.png +0 -0
  236. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  237. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  238. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  239. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  240. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  241. data/spec/dummy/config.ru +4 -0
  242. data/spec/dummy/config/application.rb +62 -0
  243. data/spec/dummy/config/boot.rb +6 -0
  244. data/spec/dummy/config/database.yml +25 -0
  245. data/spec/dummy/config/environment.rb +5 -0
  246. data/spec/dummy/config/environments/development.rb +37 -0
  247. data/spec/dummy/config/environments/production.rb +67 -0
  248. data/spec/dummy/config/environments/test.rb +37 -0
  249. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  250. data/spec/dummy/config/initializers/calagator.rb +26 -0
  251. data/spec/dummy/config/initializers/inflections.rb +15 -0
  252. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  253. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  254. data/spec/dummy/config/initializers/session_store.rb +8 -0
  255. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  256. data/spec/dummy/config/locales/en.yml +5 -0
  257. data/spec/dummy/config/routes.rb +59 -0
  258. data/spec/dummy/config/secrets.yml +83 -0
  259. data/spec/dummy/db/development.sqlite3 +0 -0
  260. data/spec/dummy/db/migrate/20150309023304_create_events.calagator.rb +18 -0
  261. data/spec/dummy/db/migrate/20150309023305_create_venues.calagator.rb +18 -0
  262. data/spec/dummy/db/migrate/20150309023306_create_sources.calagator.rb +17 -0
  263. data/spec/dummy/db/migrate/20150309023307_add_detailed_fields_to_venue.calagator.rb +20 -0
  264. data/spec/dummy/db/migrate/20150309023308_add_end_time_to_events.calagator.rb +10 -0
  265. data/spec/dummy/db/migrate/20150309023309_add_source_id_to_events.calagator.rb +10 -0
  266. data/spec/dummy/db/migrate/20150309023310_add_source_id_to_venues.calagator.rb +11 -0
  267. data/spec/dummy/db/migrate/20150309023311_add_duplicate_of_column_to_venues.calagator.rb +10 -0
  268. data/spec/dummy/db/migrate/20150309023312_add_duplicate_of_column_to_events.calagator.rb +10 -0
  269. data/spec/dummy/db/migrate/20150309023313_change_lat_long_type.calagator.rb +13 -0
  270. data/spec/dummy/db/migrate/20150309023314_add_source_reimport.calagator.rb +10 -0
  271. data/spec/dummy/db/migrate/20150309023315_change_end_time_to_duration.calagator.rb +12 -0
  272. data/spec/dummy/db/migrate/20150309023316_remove_format_type_from_source.calagator.rb +10 -0
  273. data/spec/dummy/db/migrate/20150309023317_create_updates.calagator.rb +16 -0
  274. data/spec/dummy/db/migrate/20150309023318_remove_next_update_from_source.calagator.rb +10 -0
  275. data/spec/dummy/db/migrate/20150309023319_change_duration_to_end_time.calagator.rb +12 -0
  276. data/spec/dummy/db/migrate/20150309023320_create_tags_and_taggings.calagator.rb +29 -0
  277. data/spec/dummy/db/migrate/20150309023321_create_versioned_events.calagator.rb +26 -0
  278. data/spec/dummy/db/migrate/20150309023322_create_versioned_venues.calagator.rb +33 -0
  279. data/spec/dummy/db/migrate/20150309023323_add_rrule_to_events.calagator.rb +16 -0
  280. data/spec/dummy/db/migrate/20150309023324_create_versions.calagator.rb +19 -0
  281. data/spec/dummy/db/migrate/20150309023325_add_closed_flag_to_venues.calagator.rb +10 -0
  282. data/spec/dummy/db/migrate/20150309023326_add_wifi_flag_to_venues.calagator.rb +10 -0
  283. data/spec/dummy/db/migrate/20150309023327_add_access_notes_to_venues.calagator.rb +10 -0
  284. data/spec/dummy/db/migrate/20150309023328_add_events_count_to_venues.calagator.rb +9 -0
  285. data/spec/dummy/db/migrate/20150309023329_add_venue_details_to_events.calagator.rb +10 -0
  286. data/spec/dummy/db/migrate/20150309023330_acts_as_taggable_on_migration.calagator.rb +51 -0
  287. data/spec/dummy/db/migrate/20150309023331_cleanup.calagator.rb +15 -0
  288. data/spec/dummy/db/migrate/20150309023332_specify_venues_latitude_and_longitude_precision.calagator.rb +12 -0
  289. data/spec/dummy/db/migrate/20150309023333_remove_updates.calagator.rb +14 -0
  290. data/spec/dummy/db/migrate/20150309023334_add_locked_status_to_events.calagator.rb +6 -0
  291. data/spec/dummy/db/schema.rb +95 -0
  292. data/spec/dummy/db/seeds.rb +7 -0
  293. data/spec/dummy/db/test.sqlite3 +0 -0
  294. data/spec/dummy/doc/README_FOR_APP +2 -0
  295. data/spec/dummy/log/development.log +273 -0
  296. data/spec/dummy/public/404.html +26 -0
  297. data/spec/dummy/public/422.html +26 -0
  298. data/spec/dummy/public/500.html +25 -0
  299. data/spec/dummy/public/favicon.ico +0 -0
  300. data/spec/dummy/public/robots.txt +5 -0
  301. data/spec/dummy/script/rails +6 -0
  302. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  303. data/spec/dummy/test/test_helper.rb +13 -0
  304. data/spec/factories.rb +93 -0
  305. data/spec/features/add_event_spec.rb +99 -0
  306. data/spec/features/add_venue_spec.rb +34 -0
  307. data/spec/features/admin_auth_spec.rb +22 -0
  308. data/spec/features/admin_lock_event_spec.rb +41 -0
  309. data/spec/features/import_events_from_feed_spec.rb +43 -0
  310. data/spec/features/managing_event_spec.rb +111 -0
  311. data/spec/features/managing_venue_spec.rb +71 -0
  312. data/spec/features/search_event_spec.rb +27 -0
  313. data/spec/helpers/calagator/application_helper_spec.rb +82 -0
  314. data/spec/helpers/calagator/events_helper_spec.rb +172 -0
  315. data/spec/helpers/calagator/google_event_export_helper_spec.rb +70 -0
  316. data/spec/helpers/calagator/sources_helper_spec.rb +12 -0
  317. data/spec/helpers/calagator/tags_helper_spec.rb +85 -0
  318. data/spec/helpers/calagator/time_range_helper_spec.rb +59 -0
  319. data/spec/lib/calagator/blacklist_validator_spec.rb +65 -0
  320. data/spec/lib/calagator/decode_html_entities_hack_spec.rb +54 -0
  321. data/spec/lib/calagator/settings_spec.rb +20 -0
  322. data/spec/lib/calagator/url_prefixer_spec.rb +33 -0
  323. data/spec/lib/secrets_reader_spec.rb +65 -0
  324. data/spec/models/calagator/event/cloner_spec.rb +43 -0
  325. data/spec/models/calagator/event/overview_spec.rb +79 -0
  326. data/spec/models/calagator/event/search_spec.rb +103 -0
  327. data/spec/models/calagator/event_search_spec.rb +149 -0
  328. data/spec/models/calagator/event_spec.rb +859 -0
  329. data/spec/models/calagator/source/parser_facebook_spec.rb +73 -0
  330. data/spec/models/calagator/source/parser_hcal_spec.rb +69 -0
  331. data/spec/models/calagator/source/parser_ical_non_standard_spec.rb +91 -0
  332. data/spec/models/calagator/source/parser_ical_spec.rb +322 -0
  333. data/spec/models/calagator/source/parser_meetup_spec.rb +69 -0
  334. data/spec/models/calagator/source/parser_plancast_spec.rb +53 -0
  335. data/spec/models/calagator/source/parser_spec.rb +238 -0
  336. data/spec/models/calagator/source_spec.rb +135 -0
  337. data/spec/models/calagator/venue/search_spec.rb +92 -0
  338. data/spec/models/calagator/venue_search_spec.rb +124 -0
  339. data/spec/models/calagator/venue_spec.rb +346 -0
  340. data/spec/models/tag_spec.rb +35 -0
  341. data/spec/rails_helper.rb +39 -0
  342. data/spec/spec_helper.rb +140 -0
  343. data/spec/support/disable_geocoding.rb +1 -0
  344. data/spec/support/http_samples.rb +5 -0
  345. data/spec/support/samples/facebook.json +23 -0
  346. data/spec/support/samples/hcal_basic.xml +6 -0
  347. data/spec/support/samples/hcal_dup_event_dup_venue.xml +13 -0
  348. data/spec/support/samples/hcal_event_duplicates_fixture.xml +13 -0
  349. data/spec/support/samples/hcal_event_wo_lat_and_long.xml +14 -0
  350. data/spec/support/samples/hcal_multiple.xml +16 -0
  351. data/spec/support/samples/hcal_same_event_twice_with_different_venues.xml +12 -0
  352. data/spec/support/samples/hcal_single.xml +8 -0
  353. data/spec/support/samples/hcal_two_identical_events.xml +14 -0
  354. data/spec/support/samples/hcal_upcoming_v1.html +412 -0
  355. data/spec/support/samples/hcal_upcoming_v2.html +749 -0
  356. data/spec/support/samples/hcal_upcoming_v3.html +685 -0
  357. data/spec/support/samples/hcal_upcoming_v4.html +761 -0
  358. data/spec/support/samples/ical_apple.ics +22 -0
  359. data/spec/support/samples/ical_apple_v3.ics +37 -0
  360. data/spec/support/samples/ical_basic.ics +15 -0
  361. data/spec/support/samples/ical_basic_with_duration.ics +16 -0
  362. data/spec/support/samples/ical_event_with_squashed_venue.ics +12 -0
  363. data/spec/support/samples/ical_eventful_many.ics +504 -0
  364. data/spec/support/samples/ical_gmt.ics +12 -0
  365. data/spec/support/samples/ical_google.ics +786 -0
  366. data/spec/support/samples/ical_multiple_calendars.ics +111 -0
  367. data/spec/support/samples/ical_upcoming.ics +36 -0
  368. data/spec/support/samples/ical_upcoming_many.ics +682 -0
  369. data/spec/support/samples/ical_upcoming_v2.ics +43 -0
  370. data/spec/support/samples/ical_z.ics +10 -0
  371. data/spec/support/samples/meetup.ics +16 -0
  372. data/spec/support/samples/meetup.json +31 -0
  373. data/spec/support/samples/plancast.ics +59 -0
  374. data/spec/support/samples/plancast.json +51 -0
  375. data/spec/support/samples/plancast_with_missing_venue.json +39 -0
  376. data/spec/support/samples/upcoming_v1.xml +8 -0
  377. data/spec/support/samples/upcoming_v2_with_invalid_utc_dates.xml +8 -0
  378. data/spec/support/time_convenience_methods.rb +8 -0
  379. data/spec/support/time_zones.rb +12 -0
  380. data/spec/support/url_helpers.rb +5 -0
  381. data/spec/support/wait_for_ajax.rb +15 -0
  382. data/spec/support/webmock.rb +8 -0
  383. data/spec/travis_spec.rb +7 -0
  384. metadata +1194 -0
@@ -0,0 +1,6 @@
1
+ desc "Reindex Calagator models with Sunspot"
2
+ task "sunspot:reindex:calagator" => :environment do
3
+ Rake.application.invoke_task('sunspot:solr:reindex[500,Event]')
4
+ Rake.application.invoke_task('sunspot:solr:reindex[500,Venue]')
5
+ end
6
+
@@ -0,0 +1,19 @@
1
+ require "wait_for_solr"
2
+
3
+ task "sunspot:solr:start_with_waiting" => :environment do
4
+ port = Sunspot::Rails.configuration.port
5
+
6
+ next puts "Solr already running" if WaitForSolr.running_on?(port)
7
+
8
+ puts "Starting Solr ..."
9
+
10
+ # is namespaced within app when invoked from the engine repo
11
+ task = Rake::Task.task_defined?("app:sunspot:solr:start") ? "app:sunspot:solr:start" : "sunspot:solr:start"
12
+ Rake.application.invoke_task(task)
13
+
14
+ print "Waiting for Solr "
15
+ WaitForSolr.on(port, 30) { print "." }
16
+
17
+ puts " done"
18
+ end
19
+
@@ -0,0 +1,13 @@
1
+ desc "Update counter caches"
2
+ task :update_counter_caches => :environment do
3
+ # Update the Venue#events_count
4
+ total = Venue.count
5
+ Venue.all.each do |venue|
6
+ cached = venue.events_count
7
+ actual = venue.events.count
8
+ if actual != cached
9
+ puts "Updating Venue ##{venue.id} from #{cached} cached to #{actual} actual -- #{venue.title}"
10
+ Venue.connection.update("UPDATE venues SET events_count = #{actual} WHERE id = #{venue.id}")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ <%%= semantic_form_for @<%= singular_name %> do |f| %>
2
+ <%%= f.inputs do %>
3
+ <%- attributes.each do |attribute| -%>
4
+ <%%= f.input :<%= attribute.name %> %>
5
+ <%- end -%>
6
+ <%% end %>
7
+
8
+ <%%= f.buttons do %>
9
+ <%%= f.commit_button %>
10
+ <%% end %>
11
+ <%% end %>
@@ -0,0 +1,17 @@
1
+ # = ThemeReader
2
+ #
3
+ # Returns name of the theme to use.
4
+
5
+ class ThemeReader
6
+ def self.read
7
+ rails_root = Rails.root rescue File.dirname(File.dirname(__FILE__))
8
+ theme_txt = "#{rails_root}/config/theme.txt"
9
+ if ENV["THEME"]
10
+ ENV["THEME"]
11
+ elsif File.exist?(theme_txt)
12
+ File.read(theme_txt).strip
13
+ else
14
+ "default"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require "timeout"
2
+
3
+ class WaitForSolr < Struct.new(:port, :timeout)
4
+ def self.on port, timeout, &block
5
+ new(port, timeout).wait &block
6
+ end
7
+
8
+ def self.running_on? port
9
+ new(port).responding?
10
+ end
11
+
12
+ def wait &block
13
+ Timeout::timeout timeout do
14
+ until responding? do
15
+ block.call
16
+ sleep 1
17
+ end
18
+ end
19
+ end
20
+
21
+ def responding?
22
+ system %(curl -o /dev/null "http://localhost:#{port}/solr" > /dev/null 2>&1)
23
+ end
24
+ end
25
+
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ module Calagator
4
+
5
+ describe ApplicationController, :type => :controller do
6
+ describe "#append_flash" do
7
+ before :each do
8
+ flash.clear
9
+ end
10
+
11
+ it "should set flash message if one isn't set already" do
12
+ controller.send(:append_flash, :failure, "Hello.")
13
+ expect(flash[:failure]).to eq "Hello."
14
+ end
15
+
16
+ it "should append flash message if one is already set" do
17
+ controller.send(:append_flash, :failure, "Hello.")
18
+ controller.send(:append_flash, :failure, "World.")
19
+ expect(flash[:failure]).to eq "Hello. World."
20
+ end
21
+ end
22
+
23
+ describe "#help" do
24
+ it "should respond to a view helper method" do
25
+ expect(controller.send(:help)).to respond_to :link_to
26
+ end
27
+
28
+ it "should not respond to an invalid method" do
29
+ expect(controller.send(:help)).not_to respond_to :no_such_method
30
+ end
31
+ end
32
+
33
+ describe "#escape_once" do
34
+ let(:raw) { "this & that" }
35
+ let(:escaped) { "this &amp; that" }
36
+
37
+ it "should escape raw string" do
38
+ expect(controller.send(:escape_once, raw)).to eq escaped
39
+ end
40
+
41
+ it "should not escape an already escaped string" do
42
+ expect(controller.send(:escape_once, escaped)).to eq escaped
43
+ end
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,794 @@
1
+ require 'spec_helper'
2
+ require './spec/controllers/squash_many_duplicates_examples'
3
+
4
+ module Calagator
5
+
6
+ describe EventsController, :type => :controller do
7
+ routes { Calagator::Engine.routes }
8
+
9
+ describe "#index" do
10
+ render_views
11
+
12
+ describe "as HTML" do
13
+ it "should produce HTML" do
14
+ get :index, :format => "html"
15
+
16
+ expect(response.body).to have_selector "table.event_table"
17
+ end
18
+ end
19
+
20
+ describe "as XML" do
21
+ describe "without events" do
22
+ before do
23
+ get :index, :format => "xml"
24
+
25
+ @struct = Hash.from_xml(response.body)["events"]
26
+ end
27
+
28
+ it "should not have entries" do
29
+ expect(@struct).to be_blank
30
+ end
31
+ end
32
+
33
+ describe "with events" do
34
+ before do
35
+ FactoryGirl.create(:event, :with_venue)
36
+ FactoryGirl.create(:event, :with_venue)
37
+
38
+ get :index, :format => "xml"
39
+
40
+ @struct = Hash.from_xml(response.body)["events"]
41
+ end
42
+
43
+ it "should return an array" do
44
+ expect(@struct).to be_a_kind_of Array
45
+ end
46
+
47
+ it "should have entries" do
48
+ expect(@struct).to be_present
49
+ end
50
+
51
+ it "should include venue details" do
52
+ event = @struct.first
53
+ venue = event["venue"]
54
+ venue_title = venue["title"] # Why XML? Why?
55
+ expect(venue_title).to be_a_kind_of String
56
+ expect(venue_title).to be_present
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "as JSON" do
62
+ it "should accept a JSONP callback" do
63
+ post :index, :format => "json", :callback => "some_function"
64
+
65
+ expect(response.body.split("\n").join).to match /^\s*some_function\(.*\);?\s*$/
66
+ end
67
+
68
+ describe "without events" do
69
+ before do
70
+ post :index, :format => "json"
71
+
72
+ @struct = ActiveSupport::JSON.decode(response.body)
73
+ end
74
+
75
+ it "should return an array" do
76
+ expect(@struct).to be_a_kind_of Array
77
+ end
78
+
79
+ it "should not have entries" do
80
+ expect(@struct).to be_empty
81
+ end
82
+ end
83
+
84
+ describe "with events" do
85
+ before do
86
+ @event = FactoryGirl.create(:event, :with_venue)
87
+ @venue = @event.venue
88
+
89
+ post :index, :format => "json"
90
+
91
+ @struct = ActiveSupport::JSON.decode(response.body)
92
+ end
93
+
94
+ it "should return an array" do
95
+ expect(@struct).to be_a_kind_of Array
96
+ end
97
+
98
+ it "should return an event" do
99
+ event = @struct.first
100
+ expect(event['id']).to eq @event.id
101
+ expect(event['title']).to eq @event.title
102
+ end
103
+
104
+ it "should return an event's venue" do
105
+ event = @struct.first
106
+ venue = event['venue']
107
+
108
+ expect(venue['id']).to eq @venue.id
109
+ expect(venue['title']).to eq @venue.title
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "as ATOM" do
115
+ describe "without events" do
116
+ before do
117
+ post :index, :format => "atom"
118
+ @struct = Hash.from_xml(response.body)
119
+ end
120
+
121
+ it "should be a feed" do
122
+ expect(@struct['feed']['xmlns']).to be_present
123
+ end
124
+
125
+ it "should not have events" do
126
+ expect(@struct['feed']['entry']).to be_blank
127
+ end
128
+ end
129
+
130
+ describe "with events" do
131
+ before do
132
+ FactoryGirl.create(:event, :with_venue)
133
+ FactoryGirl.create(:event, :with_venue)
134
+
135
+ post :index, :format => "atom"
136
+
137
+ @struct = Hash.from_xml(response.body)
138
+ end
139
+
140
+ let(:entries) { @struct["feed"]["entry"] }
141
+
142
+ it "should be a feed" do
143
+ expect(@struct['feed']['xmlns']).to be_present
144
+ end
145
+
146
+ it "should have entries" do
147
+ expect(entries).to be_present
148
+ end
149
+
150
+ it "should have an event" do
151
+ entry = entries.first
152
+ record = Event.find(entry['id'][%r{(\d+)$}, 1])
153
+
154
+ expect(Nokogiri.parse(entry['content']).search('.description p').inner_html).to eq record.description
155
+ expect(entry['end_time']).to eq record.end_time.xmlschema
156
+ expect(entry['start_time']).to eq record.start_time.xmlschema
157
+ expect(entry['summary']).to be_present
158
+ expect(entry['title']).to eq record.title
159
+ expect(entry['updated']).to eq record.updated_at.xmlschema
160
+ expect(entry['url']).to eq event_url(record)
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "as iCalendar" do
166
+ describe "without events" do
167
+ before do
168
+ post :index, :format => "ics"
169
+ end
170
+
171
+ it "should have a calendar" do
172
+ expect(response.body).to match /BEGIN:VCALENDAR/
173
+ end
174
+
175
+ it "should not have events" do
176
+ expect(response.body).not_to match /BEGIN:VEVENT/
177
+ end
178
+ end
179
+
180
+ describe "with events" do
181
+ before do
182
+ @current_event = FactoryGirl.create(:event, :start_time => today + 1.hour)
183
+ @past_event = FactoryGirl.create(:event, :start_time => today - 1.hour)
184
+
185
+ post :index, :format => "ics"
186
+ end
187
+
188
+ it "should have a calendar" do
189
+ expect(response.body).to match /BEGIN:VCALENDAR/
190
+ end
191
+
192
+ it "should have events" do
193
+ expect(response.body).to match /BEGIN:VEVENT/
194
+ end
195
+
196
+ it "should render all future events" do
197
+ expect(response.body).to match /SUMMARY:#{@current_event.title}/
198
+ end
199
+
200
+ it "should not render past events" do
201
+ expect(response.body).not_to match(/SUMMARY:#{@past_event.title}/)
202
+ end
203
+ end
204
+ end
205
+
206
+ describe "and filtering by date range" do
207
+ [:start, :end].each do |date_kind|
208
+ describe "for #{date_kind} date" do
209
+ let(:start_date) { Date.parse("2010-01-01") }
210
+ let(:end_date) { Date.parse("2010-04-01") }
211
+ let(:date_field) { "#{date_kind}_date" }
212
+
213
+ around do |example|
214
+ Timecop.freeze(start_date) do
215
+ example.run
216
+ end
217
+ end
218
+
219
+ it "should use the default if not given the parameter" do
220
+ get :index, :date => {}
221
+ expect(assigns[date_field]).to eq send(date_field)
222
+ expect(flash[:failure]).to be_nil
223
+ end
224
+
225
+ it "should use the default if given a malformed parameter" do
226
+ get :index, :date => "omgkittens"
227
+ expect(assigns[date_field]).to eq send(date_field)
228
+ expect(response.body).to have_selector(".flash_failure", text: 'invalid')
229
+ end
230
+
231
+ it "should use the default if given a missing parameter" do
232
+ get :index, :date => {:foo => "bar"}
233
+ expect(assigns[date_field]).to eq send(date_field)
234
+ expect(response.body).to have_selector(".flash_failure", text: 'invalid')
235
+ end
236
+
237
+ it "should use the default if given an empty parameter" do
238
+ get :index, :date => {date_kind => ""}
239
+ expect(assigns[date_field]).to eq send(date_field)
240
+ expect(response.body).to have_selector(".flash_failure", text: 'invalid')
241
+ end
242
+
243
+ it "should use the default if given an invalid parameter" do
244
+ get :index, :date => {date_kind => "omgkittens"}
245
+ expect(assigns[date_field]).to eq send(date_field)
246
+ expect(response.body).to have_selector(".flash_failure", text: 'invalid')
247
+ end
248
+
249
+ it "should use the value if valid" do
250
+ expected = Date.yesterday
251
+ get :index, :date => {date_kind => expected.to_s("%Y-%m-%d")}
252
+ expect(assigns[date_field]).to eq expected
253
+ end
254
+ end
255
+ end
256
+
257
+ it "should return matching events" do
258
+ # Given
259
+ matching = [
260
+ Event.create!(
261
+ :title => "matching1",
262
+ :start_time => Time.zone.parse("2010-01-16 00:00"),
263
+ :end_time => Time.zone.parse("2010-01-16 01:00")
264
+ ),
265
+ Event.create!(:title => "matching2",
266
+ :start_time => Time.zone.parse("2010-01-16 23:00"),
267
+ :end_time => Time.zone.parse("2010-01-17 00:00")
268
+ ),
269
+ ]
270
+
271
+ non_matching = [
272
+ Event.create!(
273
+ :title => "nonmatchingbefore",
274
+ :start_time => Time.zone.parse("2010-01-15 23:00"),
275
+ :end_time => Time.zone.parse("2010-01-15 23:59")
276
+ ),
277
+ Event.create!(
278
+ :title => "nonmatchingafter",
279
+ :start_time => Time.zone.parse("2010-01-17 00:01"),
280
+ :end_time => Time.zone.parse("2010-01-17 01:00")
281
+ ),
282
+ ]
283
+
284
+ # When
285
+ get :index, :date => {:start => "2010-01-16", :end => "2010-01-16"}
286
+ results = assigns[:events]
287
+
288
+ # Then
289
+ expect(results.size).to eq 2
290
+ expect(results).to eq matching
291
+ end
292
+ end
293
+ end
294
+
295
+ describe "#show" do
296
+ it "should show an event" do
297
+ event = Event.new(:start_time => now)
298
+ expect(Event).to receive(:find).and_return(event)
299
+
300
+ get "show", :id => 1234
301
+ expect(response).to be_success
302
+ end
303
+
304
+ it "should redirect from a duplicate event to its master" do
305
+ master = FactoryGirl.create(:event, id: 4321)
306
+ event = Event.new(:start_time => now, :duplicate_of => master)
307
+ expect(Event).to receive(:find).and_return(event)
308
+
309
+ get "show", :id => 1234
310
+ expect(response).to redirect_to(event_path(master))
311
+ end
312
+
313
+ it "should show an error when asked to display a non-existent event" do
314
+ expect(Event).to receive(:find).and_raise(ActiveRecord::RecordNotFound)
315
+
316
+ get "show", :id => 1234
317
+ expect(response).to redirect_to(events_path)
318
+ expect(flash[:failure]).not_to be_blank
319
+ end
320
+ end
321
+
322
+ describe "when creating and updating events" do
323
+ before do
324
+ @params = {
325
+ "end_date" => "2008-06-04",
326
+ "start_date" => "2008-06-03",
327
+ "event" => {
328
+ "title" => "MyVenue",
329
+ "url" => "http://my.venue",
330
+ "description" => "Wheeeee"
331
+ },
332
+ "end_time" => "",
333
+ "start_time" => ""
334
+ }.with_indifferent_access
335
+ @venue = FactoryGirl.build(:venue)
336
+ @event = FactoryGirl.build(:event, :venue => @venue)
337
+ end
338
+
339
+ describe "#new" do
340
+ it "should display form for creating new event" do
341
+ get "new"
342
+ expect(response).to be_success
343
+ expect(response).to render_template :new
344
+ end
345
+ end
346
+
347
+ describe "#create" do
348
+ render_views
349
+
350
+ it "should create a new event without a venue" do
351
+ @params[:event][:venue_id] = nil
352
+ post "create", @params
353
+ @event = Event.find_by_title(@params[:event][:title])
354
+ expect(response).to redirect_to(@event)
355
+ end
356
+
357
+ it "should associate a venue based on a given venue id" do
358
+ @venue.save!
359
+ @params[:event][:venue_id] = @venue.id.to_s
360
+ post "create", @params
361
+ @event = Event.find_by_title(@params[:event][:title])
362
+ expect(@event.venue).to eq(@venue)
363
+ expect(response).to redirect_to(@event)
364
+ end
365
+
366
+ it "should associate a venue based on a given venue name" do
367
+ @venue.save!
368
+ @params[:venue_name] = @venue.title
369
+ post "create", @params
370
+ @event = Event.find_by_title(@params[:event][:title])
371
+ expect(@event.venue).to eq(@venue)
372
+ expect(response).to redirect_to(@event)
373
+ end
374
+
375
+ it "should associate a venue by id when both an id and a name are provided" do
376
+ @venue.save!
377
+ @venue2 = FactoryGirl.create(:venue)
378
+ @params[:event][:venue_id] = @venue.id.to_s
379
+ @params[:venue_name] = @venue2.title
380
+ post "create", @params
381
+ @event = Event.find_by_title(@params[:event][:title])
382
+ expect(@event.venue).to eq(@venue)
383
+ expect(response).to redirect_to(@event)
384
+ end
385
+
386
+ it "should create a new event and new venue, and redirect to venue edit form" do
387
+ @params[:venue_name] = "New Venue"
388
+ post "create", @params
389
+ @event = Event.find_by_title(@params[:event][:title])
390
+ @venue = Venue.find_by_title("New Venue")
391
+ expect(@event.venue).to eq(@venue)
392
+ expect(response).to redirect_to(edit_venue_url(@venue, :from_event => @event.id))
393
+ end
394
+
395
+ it "should catch errors and redisplay the new event form" do
396
+ post "create"
397
+ expect(response).to render_template :new
398
+ end
399
+
400
+ it "should stop evil robots" do
401
+ post "create", :trap_field => "I AM AN EVIL ROBOT, I EAT OLD PEOPLE'S MEDICINE FOR FOOD!"
402
+ expect(response).to render_template :new
403
+ expect(flash[:failure]).to match /evil robot/i
404
+ end
405
+
406
+ it "should not allow too many links in the description" do
407
+ @params[:event][:description] = <<-DESC
408
+ http://example.com
409
+ https://example.com
410
+ http://example.net
411
+ https://example.net
412
+ DESC
413
+ post "create", @params
414
+ expect(response).to render_template :new
415
+ expect(flash[:failure]).to match /too many links/i
416
+ end
417
+
418
+ it "should accept HTTP-rich presentation descriptions without too many links" do
419
+ @params[:event][:description] = <<-DESC
420
+ I hereby offer to give a presentation at the August ruby meeting about the faraday
421
+ gem (https://github.com/lostisland/faraday) and how compares to or compliments other
422
+ HTTP client libraries such as httparty (https://github.com/jnunemaker/httparty).
423
+
424
+ --
425
+
426
+ I wouldn't mind seeing a PDX.pm talk about HTTP::Tiny vs Net::HTTP::Tiny vs Net::HTTP
427
+ vs HTTP::Client vs HTTP::Client::Parallel
428
+ DESC
429
+ post "create", @params
430
+ expect(flash[:failure]).to be_nil
431
+ end
432
+
433
+ it "should allow the user to preview the event" do
434
+ @params[:preview] = "Preview"
435
+ post "create", @params
436
+ expect(response).to render_template :new
437
+ expect(response.body).to have_selector '#event_preview'
438
+ end
439
+
440
+ it "should create an event for an existing venue" do
441
+ venue = FactoryGirl.create(:venue)
442
+
443
+ post "create",
444
+ :start_time => now.strftime("%Y-%m-%d"),
445
+ :end_time => (now + 1.hour).strftime("%Y-%m-%d"),
446
+ :event => {
447
+ :title => "My Event",
448
+ :tag_list => ",,foo,bar, baz,",
449
+ },
450
+ :venue_name => venue.title
451
+
452
+ expect(response).to be_redirect
453
+
454
+ expect(flash[:success]).to be_present
455
+
456
+ event = assigns[:event]
457
+ expect(event.title).to eq "My Event"
458
+ expect(event.venue.title).to eq venue.title
459
+ expect(event.venue.id).to eq venue.id
460
+ expect(event.tag_list.to_a.sort).to eq %w[bar baz foo]
461
+ end
462
+ end
463
+
464
+ describe "#update" do
465
+ before(:each) do
466
+ @event = FactoryGirl.create(:event, :with_venue, id: 42)
467
+ @venue = @event.venue
468
+ @params.merge!(id: 42)
469
+ end
470
+
471
+ it "should display form for editing event" do
472
+ get "edit", id: 42
473
+ expect(response).to be_success
474
+ expect(response).to render_template :edit
475
+ end
476
+
477
+ it "should update an event without a venue" do
478
+ @event.venue = nil
479
+ put "update", @params
480
+ expect(response).to redirect_to(@event)
481
+ end
482
+
483
+ it "should associate a venue based on a given venue id" do
484
+ @venue = FactoryGirl.create(:venue)
485
+ @params[:event][:venue_id] = @venue.id.to_s
486
+ put "update", @params
487
+ expect(@event.reload.venue).to eq(@venue)
488
+ expect(response).to redirect_to(@event)
489
+ end
490
+
491
+ it "should associate a venue based on a given venue name" do
492
+ @venue = FactoryGirl.create(:venue)
493
+ @params[:venue_name] = @venue.title
494
+ put "update", @params
495
+ expect(@event.reload.venue).to eq(@venue)
496
+ expect(response).to redirect_to(@event)
497
+ end
498
+
499
+ it "should associate a venue by id when both an id and a name are provided" do
500
+ @venue = FactoryGirl.create(:venue)
501
+ @venue2 = FactoryGirl.create(:venue)
502
+ @params[:event][:venue_id] = @venue.id.to_s
503
+ @params[:venue_name] = @venue2.title
504
+ put "update", @params
505
+ expect(@event.reload.venue).to eq(@venue)
506
+ expect(response).to redirect_to(@event)
507
+ end
508
+
509
+ it "should update an event and create a new venue, and redirect to the venue edit form" do
510
+ @params[:venue_name] = "New Venue"
511
+ put "update", @params
512
+ @venue = Venue.find_by_title("New Venue")
513
+ expect(response).to redirect_to(edit_venue_url(@venue, :from_event => @event.id))
514
+ end
515
+
516
+ it "should catch errors and redisplay the new event form" do
517
+ @params[:event][:title] = nil
518
+ put "update", @params
519
+ expect(response).to render_template :edit
520
+ end
521
+
522
+ it "should stop evil robots" do
523
+ @params[:trap_field] = "I AM AN EVIL ROBOT, I EAT OLD PEOPLE'S MEDICINE FOR FOOD!"
524
+ put "update", @params
525
+ expect(response).to render_template :edit
526
+ expect(flash[:failure]).to match /evil robot/i
527
+ end
528
+
529
+ it "should not allow too many links in the description" do
530
+ @params[:event][:description] = <<-DESC
531
+ http://example.com
532
+ https://example.com
533
+ http://example.net
534
+ https://example.net
535
+ DESC
536
+ put "update", @params
537
+ expect(response).to render_template :edit
538
+ expect(flash[:failure]).to match /too many links/i
539
+ end
540
+
541
+ it "should allow the user to preview the event" do
542
+ put "update", @params.merge(:preview => "Preview")
543
+ expect(response).to render_template :edit
544
+ end
545
+
546
+ it "should not allow a user to update a locked event" do
547
+ @event.lock_editing!
548
+ put "update", @params
549
+ expect(response).to be_redirect
550
+ expect(flash[:failure]).to match /not permitted/i
551
+ end
552
+ end
553
+
554
+ describe "#clone" do
555
+ before do
556
+ @event = FactoryGirl.create(:event)
557
+
558
+ allow(Event).to receive(:find).and_return(@event)
559
+
560
+ get "clone", :id => 1
561
+ end
562
+
563
+ it "should build an unsaved record" do
564
+ record = assigns[:event]
565
+ expect(record).to be_a_new_record
566
+ expect(record.id).to be_nil
567
+ end
568
+
569
+ it "should build a cloned record similar to the existing record" do
570
+ record = assigns[:event]
571
+ %w[title description venue_id venue_details].each do |field|
572
+ expect(record.attributes[field]).to eq @event.attributes[field]
573
+ end
574
+ end
575
+
576
+ it "should display a new event form" do
577
+ expect(response).to be_success
578
+ expect(response).to render_template :new
579
+ end
580
+
581
+ it "should have notice with cloning instructions" do
582
+ expect(flash[:success]).to match /clone/i
583
+ end
584
+ end
585
+ end
586
+
587
+ context "with admin auth for duplicates" do
588
+ before do
589
+ credentials = ActionController::HttpAuthentication::Basic.encode_credentials SECRETS.admin_username, SECRETS.admin_password
590
+ request.env['HTTP_AUTHORIZATION'] = credentials
591
+ end
592
+
593
+ describe "#duplicates" do
594
+ render_views
595
+
596
+ it "should find current duplicates and not past duplicates" do
597
+ current_master = FactoryGirl.create(:event, :title => "Current")
598
+ current_duplicate = FactoryGirl.create(:event, :title => current_master.title)
599
+
600
+ past_master = FactoryGirl.create(:event, :title => "Past", :start_time => now - 2.days)
601
+ past_duplicate = FactoryGirl.create(:event, :title => past_master.title, :start_time => now - 1.day)
602
+
603
+ get 'duplicates', :type => 'title'
604
+
605
+ # Current duplicates
606
+ assigns[:grouped_events].select{|keys,values| keys.include?(current_master.title)}.tap do |events|
607
+ expect(events).not_to be_empty
608
+ expect(events.first.last.size).to eq 2
609
+ end
610
+
611
+ # Past duplicates
612
+ expect(assigns[:grouped_events].select{|keys,values| keys.include?(past_master.title)}).to be_empty
613
+ end
614
+
615
+ it "should redirect duplicate events to their master" do
616
+ event_master = FactoryGirl.create(:event)
617
+ event_duplicate = FactoryGirl.create(:event)
618
+
619
+ get 'show', :id => event_duplicate.id
620
+ expect(response).not_to be_redirect
621
+ expect(assigns(:event).id).to eq event_duplicate.id
622
+
623
+ event_duplicate.duplicate_of = event_master
624
+ event_duplicate.save!
625
+
626
+ get 'show', :id => event_duplicate.id
627
+ expect(response).to be_redirect
628
+ expect(response).to redirect_to(event_url(event_master.id))
629
+ end
630
+
631
+ it "should display an error message if given invalid arguments" do
632
+ get 'duplicates', :type => 'omgwtfbbq'
633
+
634
+ expect(response).to be_success
635
+ expect(response.body).to have_selector('.failure', text: 'omgwtfbbq')
636
+ end
637
+ end
638
+
639
+ context do
640
+ include_examples "#squash_many_duplicates", :event
641
+ end
642
+ end
643
+
644
+ describe "#search" do
645
+ describe "when returning results" do
646
+ render_views
647
+
648
+ let!(:current_event) { FactoryGirl.create(:event, :with_venue, title: "MyQuery") }
649
+ let!(:current_event_2) { FactoryGirl.create(:event, :with_venue, description: "WOW myquery!") }
650
+ let!(:past_event) { FactoryGirl.create(:event, :with_venue, title: "old myquery") }
651
+
652
+ describe "in HTML format" do
653
+ before do
654
+ post :search, :query => "myquery", :format => "html"
655
+ end
656
+
657
+ it "should assign search result" do
658
+ expect(assigns[:search]).to be_a Event::Search
659
+ end
660
+
661
+ it "should assign matching events" do
662
+ expect(assigns[:events]).to match_array([current_event, current_event_2, past_event])
663
+ end
664
+
665
+ it "should render matching events" do
666
+ have_selector "table.event_table" do
667
+ have_selector ".vevent a.summary", :href => event_url(results[:past])
668
+ have_selector ".vevent a.summary", :href => event_url(results[:current])
669
+ end
670
+ end
671
+
672
+ describe "sidebar" do
673
+ it "should have iCalendar feed" do
674
+ have_selector ".sidebar a", :href => search_events_url(:query => @query, :format => "ics", :protocol => "webcal")
675
+ end
676
+
677
+ it "should have Atom feed" do
678
+ have_selector ".sidebar a", :href => search_events_url(:query => @query, :format => "atom")
679
+ end
680
+
681
+ it "should have Google subscription" do
682
+ ics_url = search_events_url(query: @query, format: 'ics')
683
+ google_url = "https://www.google.com/calendar/render?cid=#{ics_url}"
684
+ have_selector ".sidebar a", href: google_url
685
+ end
686
+ end
687
+ end
688
+
689
+ describe "in XML format" do
690
+ it "should produce XML" do
691
+ post :search, :query => "myquery", :format => "xml"
692
+
693
+ hash = Hash.from_xml(response.body)
694
+ expect(hash["events"]).to be_a_kind_of Array
695
+ end
696
+
697
+ it "should include venue details" do
698
+ post :search, :query => "myquery", :format => "xml"
699
+
700
+ hash = Hash.from_xml(response.body)
701
+ event = hash["events"].first
702
+ venue = event["venue"]
703
+ venue_title = venue["title"]
704
+ expect(venue_title).to be_a_kind_of String
705
+ expect(venue_title.length).to be_present
706
+ end
707
+ end
708
+
709
+ describe "in JSON format" do
710
+ it "should produce JSON" do
711
+ post :search, :query => "myquery", :format => "json"
712
+
713
+ struct = ActiveSupport::JSON.decode(response.body)
714
+ expect(struct).to be_a_kind_of Array
715
+ end
716
+
717
+ it "should accept a JSONP callback" do
718
+ post :search, :query => "myquery", :format => "json", :callback => "some_function"
719
+
720
+ expect(response.body).to match /^\s*some_function\(.*\);?\s*$/
721
+ end
722
+
723
+ it "should include venue details" do
724
+ post :search, :query => "myquery", :format => "json"
725
+
726
+ struct = ActiveSupport::JSON.decode(response.body)
727
+ event = struct.first
728
+ expect(event["venue"]["title"]).to be_a_kind_of String
729
+ expect(event["venue"]["title"].length).to be_present
730
+ end
731
+ end
732
+
733
+ describe "in ATOM format" do
734
+ it "should produce ATOM" do
735
+ post :search, :query => "myquery", :format => "atom"
736
+
737
+ hash = Hash.from_xml(response.body)
738
+ expect(hash["feed"]["entry"]).to be_a_kind_of Array
739
+ end
740
+ end
741
+
742
+ describe "in ICS format" do
743
+ it "should produce ICS" do
744
+ post :search, :query => "myquery", :format => "ics"
745
+
746
+ expect(response.body).to match /BEGIN:VEVENT/
747
+ end
748
+
749
+ it "should produce events matching the query" do
750
+ post :search, :query => "myquery", :format => "ics"
751
+ expect(response.body).to match /SUMMARY:#{current_event_2.title}/
752
+ expect(response.body).to match /SUMMARY:#{past_event.title}/
753
+ end
754
+ end
755
+
756
+ describe "failures" do
757
+ it "sets search failures in the flash message" do
758
+ allow_any_instance_of(Event::Search).to receive_messages failure_message: "OMG"
759
+ post :search
760
+ expect(flash[:failure]).to eq("OMG")
761
+ end
762
+
763
+ it "redirects to home if hard failure" do
764
+ allow_any_instance_of(Event::Search).to receive_messages hard_failure?: true
765
+ post :search
766
+ expect(response).to redirect_to(root_path)
767
+ end
768
+ end
769
+ end
770
+ end
771
+
772
+ describe "#destroy" do
773
+ it "should destroy events" do
774
+ event = FactoryGirl.build(:event)
775
+ expect(event).to receive(:destroy)
776
+ expect(Event).to receive(:find).and_return(event)
777
+
778
+ delete 'destroy', :id => 1234
779
+ expect(response).to redirect_to(events_url)
780
+ end
781
+
782
+ it "should not allow a user to destroy a locked event" do
783
+ event = FactoryGirl.create(:event)
784
+ event.lock_editing!
785
+
786
+ delete 'destroy', :id => event.id
787
+ expect(response).to be_redirect
788
+ expect(flash[:failure]).to match /not permitted/i
789
+ end
790
+
791
+ end
792
+ end
793
+
794
+ end