landable 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (310) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +15 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +56 -0
  7. data/Rakefile +22 -0
  8. data/app/controllers/concerns/landable/variables_concern.rb +38 -0
  9. data/app/controllers/landable/api/access_tokens_controller.rb +48 -0
  10. data/app/controllers/landable/api/assets_controller.rb +55 -0
  11. data/app/controllers/landable/api/categories_controller.rb +13 -0
  12. data/app/controllers/landable/api/directories_controller.rb +19 -0
  13. data/app/controllers/landable/api/page_revisions_controller.rb +38 -0
  14. data/app/controllers/landable/api/pages_controller.rb +96 -0
  15. data/app/controllers/landable/api/templates_controller.rb +33 -0
  16. data/app/controllers/landable/api/themes_controller.rb +57 -0
  17. data/app/controllers/landable/api_controller.rb +75 -0
  18. data/app/controllers/landable/application_controller.rb +4 -0
  19. data/app/controllers/landable/public/pages_controller.rb +25 -0
  20. data/app/controllers/landable/public/preview/page_revisions_controller.rb +16 -0
  21. data/app/controllers/landable/public/preview/pages_controller.rb +16 -0
  22. data/app/controllers/landable/public/sitemap_controller.rb +18 -0
  23. data/app/decorators/landable/null_page_decorator.rb +26 -0
  24. data/app/decorators/landable/page_decorator.rb +40 -0
  25. data/app/helpers/landable/application_helper.rb +13 -0
  26. data/app/helpers/landable/pages_helper.rb +20 -0
  27. data/app/models/concerns/landable/has_assets.rb +74 -0
  28. data/app/models/concerns/landable/table_name.rb +12 -0
  29. data/app/models/concerns/landable/traffic/table_name.rb +14 -0
  30. data/app/models/landable/access_token.rb +20 -0
  31. data/app/models/landable/asset.rb +63 -0
  32. data/app/models/landable/asset_search_engine.rb +20 -0
  33. data/app/models/landable/author.rb +12 -0
  34. data/app/models/landable/category.rb +27 -0
  35. data/app/models/landable/directory.rb +23 -0
  36. data/app/models/landable/page.rb +259 -0
  37. data/app/models/landable/page_revision.rb +74 -0
  38. data/app/models/landable/page_search_engine.rb +20 -0
  39. data/app/models/landable/search_engine.rb +39 -0
  40. data/app/models/landable/template.rb +24 -0
  41. data/app/models/landable/theme.rb +20 -0
  42. data/app/models/landable/traffic/access.rb +11 -0
  43. data/app/models/landable/traffic/ad_group.rb +11 -0
  44. data/app/models/landable/traffic/ad_type.rb +11 -0
  45. data/app/models/landable/traffic/attribution.rb +38 -0
  46. data/app/models/landable/traffic/bid_match_type.rb +11 -0
  47. data/app/models/landable/traffic/browser.rb +11 -0
  48. data/app/models/landable/traffic/campaign.rb +11 -0
  49. data/app/models/landable/traffic/city.rb +11 -0
  50. data/app/models/landable/traffic/content.rb +11 -0
  51. data/app/models/landable/traffic/cookie.rb +16 -0
  52. data/app/models/landable/traffic/country.rb +11 -0
  53. data/app/models/landable/traffic/creative.rb +11 -0
  54. data/app/models/landable/traffic/device.rb +11 -0
  55. data/app/models/landable/traffic/device_type.rb +11 -0
  56. data/app/models/landable/traffic/domain.rb +12 -0
  57. data/app/models/landable/traffic/event.rb +12 -0
  58. data/app/models/landable/traffic/event_type.rb +11 -0
  59. data/app/models/landable/traffic/experiment.rb +11 -0
  60. data/app/models/landable/traffic/http_method.rb +11 -0
  61. data/app/models/landable/traffic/ip_address.rb +11 -0
  62. data/app/models/landable/traffic/ip_lookup.rb +12 -0
  63. data/app/models/landable/traffic/keyword.rb +11 -0
  64. data/app/models/landable/traffic/location.rb +11 -0
  65. data/app/models/landable/traffic/match_type.rb +11 -0
  66. data/app/models/landable/traffic/medium.rb +11 -0
  67. data/app/models/landable/traffic/mime_type.rb +11 -0
  68. data/app/models/landable/traffic/network.rb +11 -0
  69. data/app/models/landable/traffic/owner.rb +10 -0
  70. data/app/models/landable/traffic/ownership.rb +10 -0
  71. data/app/models/landable/traffic/page_view.rb +24 -0
  72. data/app/models/landable/traffic/path.rb +13 -0
  73. data/app/models/landable/traffic/placement.rb +11 -0
  74. data/app/models/landable/traffic/platform.rb +11 -0
  75. data/app/models/landable/traffic/position.rb +11 -0
  76. data/app/models/landable/traffic/query_string.rb +12 -0
  77. data/app/models/landable/traffic/referer.rb +11 -0
  78. data/app/models/landable/traffic/region.rb +11 -0
  79. data/app/models/landable/traffic/search_term.rb +11 -0
  80. data/app/models/landable/traffic/source.rb +11 -0
  81. data/app/models/landable/traffic/target.rb +11 -0
  82. data/app/models/landable/traffic/user_agent.rb +28 -0
  83. data/app/models/landable/traffic/user_agent_type.rb +11 -0
  84. data/app/models/landable/traffic/visit.rb +15 -0
  85. data/app/models/landable/traffic/visitor.rb +13 -0
  86. data/app/responders/landable/api_responder.rb +76 -0
  87. data/app/responders/landable/page_render_responder.rb +15 -0
  88. data/app/serializers/landable/access_token_serializer.rb +6 -0
  89. data/app/serializers/landable/asset_serializer.rb +14 -0
  90. data/app/serializers/landable/author_serializer.rb +5 -0
  91. data/app/serializers/landable/category_serializer.rb +5 -0
  92. data/app/serializers/landable/directory_serializer.rb +8 -0
  93. data/app/serializers/landable/page_revision_serializer.rb +15 -0
  94. data/app/serializers/landable/page_serializer.rb +31 -0
  95. data/app/serializers/landable/template_serializer.rb +5 -0
  96. data/app/serializers/landable/theme_serializer.rb +7 -0
  97. data/app/services/landable/authentication_service.rb +44 -0
  98. data/app/services/landable/registration_service.rb +13 -0
  99. data/app/services/landable/render_service.rb +86 -0
  100. data/app/services/landable/tidy_service.rb +155 -0
  101. data/app/uploaders/landable/asset_uploader.rb +9 -0
  102. data/app/validators/path_validator.rb +12 -0
  103. data/app/validators/url_validator.rb +13 -0
  104. data/app/views/templates/preview.liquid +122 -0
  105. data/bin/rails +8 -0
  106. data/bin/redb +7 -0
  107. data/config.ru +7 -0
  108. data/config/cucumber.yml +5 -0
  109. data/config/routes.rb +62 -0
  110. data/db/migrate/20130510221424_create_landable_schema.rb +338 -0
  111. data/db/migrate/20130909182713_landable_pages__add_updated_by.rb +11 -0
  112. data/db/migrate/20130909182715_landable_page_revisions__break_out_snapshot.rb +72 -0
  113. data/db/migrate/20130909191153_landable_pages__add_lock_version.rb +5 -0
  114. data/db/migrate/20131002220041_file_based_themes.rb +12 -0
  115. data/db/migrate/20131008164204_create_head_tag_on_page.rb +19 -0
  116. data/db/migrate/20131008193544_drop_status_codes_model.rb +44 -0
  117. data/db/migrate/20131028145652_add_traffic_schema.rb +276 -0
  118. data/db/migrate/20131101213623_add_dnt_column_to_visits.rb +7 -0
  119. data/db/migrate/20131104224120_add_meta_on_events.rb +7 -0
  120. data/db/migrate/20131106185946_add_index_on_page_revisions_path.rb +6 -0
  121. data/db/migrate/20131106193021_page_revisisons__path_status_code_index.rb +9 -0
  122. data/db/migrate/20131108212501_traffic_owner_ids_are_serials.rb +20 -0
  123. data/db/migrate/20131121150902_add_attribution_id_to_unique_index.rb +10 -0
  124. data/db/migrate/20131216214027_drop_browser_screenshot_tables.rb +6 -0
  125. data/db/migrate/20140128170659_file_backed_templates.rb +8 -0
  126. data/db/migrate/20140205193757_fix_status_codes.rb +24 -0
  127. data/db/migrate/20140206211322_add_response_time_to_traffic_page_views.rb +7 -0
  128. data/db/migrate/20140220170324_add_slug_to_categories.rb +14 -0
  129. data/db/migrate/20140220174630_add_abstract_and_hero_asset_to_pages_and_page_revisions.rb +8 -0
  130. data/db/migrate/20140224205516_rename_traffic_schema.rb +40 -0
  131. data/db/pgtap/pgtap.sql +9034 -0
  132. data/db/test/landable.access_tokens.sql +13 -0
  133. data/db/test/landable.assets.sql +16 -0
  134. data/db/test/landable.authors.sql +22 -0
  135. data/db/test/landable.categories.sql +9 -0
  136. data/db/test/landable.page_revisions.sql +41 -0
  137. data/db/test/landable.pages.sql +19 -0
  138. data/db/test/landable.templates.sql +15 -0
  139. data/db/test/landable.themes.sql +25 -0
  140. data/doc/schema/access_token.json +22 -0
  141. data/doc/schema/asset.json +65 -0
  142. data/doc/schema/author.json +30 -0
  143. data/doc/schema/directory.json +24 -0
  144. data/doc/schema/page.json +95 -0
  145. data/doc/schema/page_revision.json +70 -0
  146. data/doc/schema/theme.json +37 -0
  147. data/doc/schema/uuid.json +6 -0
  148. data/features/api/access_tokens.feature +84 -0
  149. data/features/api/assets.feature +46 -0
  150. data/features/api/cors.feature +25 -0
  151. data/features/api/pages.feature +42 -0
  152. data/features/api/preview.feature +16 -0
  153. data/features/api/templates.feature +33 -0
  154. data/features/api/themes.feature +33 -0
  155. data/features/liquid/body.feature +35 -0
  156. data/features/liquid/drops/categories.feature +54 -0
  157. data/features/liquid/tags.feature +168 -0
  158. data/features/public/content_types.feature +17 -0
  159. data/features/public/publishing.feature +45 -0
  160. data/features/public/status_codes.feature +25 -0
  161. data/features/public/views.feature +17 -0
  162. data/features/step_definitions/asset_steps.rb +60 -0
  163. data/features/step_definitions/core_api_steps.rb +139 -0
  164. data/features/step_definitions/debug_steps.rb +3 -0
  165. data/features/step_definitions/factory_steps.rb +124 -0
  166. data/features/step_definitions/html_steps.rb +9 -0
  167. data/features/step_definitions/liquid_steps.rb +79 -0
  168. data/features/step_definitions/revision_steps.rb +5 -0
  169. data/features/step_definitions/theme_steps.rb +43 -0
  170. data/features/support/env.rb +66 -0
  171. data/features/support/usefulness.rb +13 -0
  172. data/landable.gemspec +54 -0
  173. data/lib/generators/landable/collection/collection_generator.rb +0 -0
  174. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%.less +0 -0
  175. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/mixins.less +0 -0
  176. data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/variables.less +0 -0
  177. data/lib/generators/landable/component/component_generator.rb +0 -0
  178. data/lib/generators/landable/component/templates/javascripts/landable/%file_name%.less +0 -0
  179. data/lib/generators/landable/component/templates/stylesheets/landable/%file_name%.less +0 -0
  180. data/lib/generators/landable/install_generator.rb +19 -0
  181. data/lib/generators/landable/landable_generator.rb +22 -0
  182. data/lib/generators/templates/landable.rb +34 -0
  183. data/lib/landable.rb +30 -0
  184. data/lib/landable/configuration.rb +115 -0
  185. data/lib/landable/core_ext/ipaddr.rb +18 -0
  186. data/lib/landable/engine.rb +69 -0
  187. data/lib/landable/error.rb +16 -0
  188. data/lib/landable/inflections.rb +4 -0
  189. data/lib/landable/layout.rb +60 -0
  190. data/lib/landable/liquid.rb +27 -0
  191. data/lib/landable/liquid/asset_tags.rb +76 -0
  192. data/lib/landable/liquid/drops.rb +46 -0
  193. data/lib/landable/liquid/filters.rb +11 -0
  194. data/lib/landable/liquid/tags.rb +91 -0
  195. data/lib/landable/migration.rb +40 -0
  196. data/lib/landable/mime_types.rb +15 -0
  197. data/lib/landable/partial.rb +46 -0
  198. data/lib/landable/seeds.rb +36 -0
  199. data/lib/landable/traffic.rb +34 -0
  200. data/lib/landable/traffic/crawl_tracker.rb +9 -0
  201. data/lib/landable/traffic/noop_tracker.rb +8 -0
  202. data/lib/landable/traffic/ping_tracker.rb +9 -0
  203. data/lib/landable/traffic/scan_tracker.rb +9 -0
  204. data/lib/landable/traffic/scrape_tracker.rb +9 -0
  205. data/lib/landable/traffic/tracker.rb +283 -0
  206. data/lib/landable/traffic/user_tracker.rb +65 -0
  207. data/lib/landable/version.rb +10 -0
  208. data/lib/tasks/landable/cucumber.rake +67 -0
  209. data/lib/tasks/landable/data.rake +166 -0
  210. data/lib/tasks/landable/pgtap.rake +26 -0
  211. data/lib/tasks/landable/rdoc.rake +11 -0
  212. data/lib/tasks/landable/seed.rake +16 -0
  213. data/lib/tasks/landable/spec.rake +15 -0
  214. data/script/cucumber +10 -0
  215. data/spec/concerns/landable/has_assets_spec.rb +75 -0
  216. data/spec/concerns/landable/table_name_spec.rb +15 -0
  217. data/spec/concerns/landable/traffic/table_name_spec.rb +16 -0
  218. data/spec/controllers/concerns/landable/variables_concern_spec.rb +66 -0
  219. data/spec/controllers/landable/api/assets_controller_spec.rb +24 -0
  220. data/spec/controllers/landable/api/categories_controller_spec.rb +45 -0
  221. data/spec/controllers/landable/api/directories_controller_spec.rb +56 -0
  222. data/spec/controllers/landable/api/page_revisions_controller_spec.rb +29 -0
  223. data/spec/controllers/landable/api/pages_controller_spec.rb +271 -0
  224. data/spec/controllers/landable/api_controller_spec.rb +189 -0
  225. data/spec/controllers/public/preview/page_revisions_controller_spec.rb +41 -0
  226. data/spec/controllers/public/preview/pages_controller_spec.rb +36 -0
  227. data/spec/controllers/public/sitemap_controller_spec.rb +25 -0
  228. data/spec/decorators/page_decorator_spec.rb +90 -0
  229. data/spec/dummy/README.rdoc +28 -0
  230. data/spec/dummy/Rakefile +6 -0
  231. data/spec/dummy/app/assets/images/foo.jpg +0 -0
  232. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  233. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  234. data/spec/dummy/app/controllers/application_controller.rb +10 -0
  235. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  236. data/spec/dummy/app/controllers/priority_controller.rb +7 -0
  237. data/spec/dummy/app/helpers/application_helper.rb +5 -0
  238. data/spec/dummy/app/mailers/.keep +0 -0
  239. data/spec/dummy/app/models/.keep +0 -0
  240. data/spec/dummy/app/models/concerns/.keep +0 -0
  241. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  242. data/spec/dummy/app/views/layouts/priority.html.erb +18 -0
  243. data/spec/dummy/app/views/partials/_foobazz.html +1 -0
  244. data/spec/dummy/app/views/priority/show.html.erb +11 -0
  245. data/spec/dummy/bin/bundle +3 -0
  246. data/spec/dummy/bin/rails +4 -0
  247. data/spec/dummy/bin/rake +4 -0
  248. data/spec/dummy/config.ru +4 -0
  249. data/spec/dummy/config/application.rb +21 -0
  250. data/spec/dummy/config/boot.rb +5 -0
  251. data/spec/dummy/config/database.yml +60 -0
  252. data/spec/dummy/config/environment.rb +5 -0
  253. data/spec/dummy/config/environments/development.rb +29 -0
  254. data/spec/dummy/config/environments/production.rb +80 -0
  255. data/spec/dummy/config/environments/test.rb +42 -0
  256. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  257. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  258. data/spec/dummy/config/initializers/inflections.rb +16 -0
  259. data/spec/dummy/config/initializers/landable.rb +22 -0
  260. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  261. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  262. data/spec/dummy/config/initializers/session_store.rb +3 -0
  263. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  264. data/spec/dummy/config/locales/en.yml +23 -0
  265. data/spec/dummy/config/routes.rb +4 -0
  266. data/spec/dummy/db/structure.sql +3736 -0
  267. data/spec/dummy/lib/assets/.keep +0 -0
  268. data/spec/dummy/log/.keep +0 -0
  269. data/spec/dummy/public/404.html +58 -0
  270. data/spec/dummy/public/422.html +58 -0
  271. data/spec/dummy/public/500.html +57 -0
  272. data/spec/dummy/public/favicon.ico +0 -0
  273. data/spec/factories/asset.rb +29 -0
  274. data/spec/factories/authors.rb +12 -0
  275. data/spec/factories/category.rb +6 -0
  276. data/spec/factories/page_revision.rb +6 -0
  277. data/spec/factories/pages.rb +33 -0
  278. data/spec/factories/template.rb +13 -0
  279. data/spec/factories/theme.rb +14 -0
  280. data/spec/fixtures/assets/cthulhu.jpg +0 -0
  281. data/spec/fixtures/assets/panda.png +0 -0
  282. data/spec/fixtures/assets/sloth.png +0 -0
  283. data/spec/fixtures/assets/small.pdf +0 -0
  284. data/spec/helpers/pages_helper_spec.rb +35 -0
  285. data/spec/lib/landable/configuration_spec.rb +20 -0
  286. data/spec/lib/landable/layout_spec.rb +25 -0
  287. data/spec/lib/landable/liquid_spec.rb +24 -0
  288. data/spec/lib/landable/migration_spec.rb +51 -0
  289. data/spec/lib/landable/partial_spec.rb +84 -0
  290. data/spec/lib/landable/tracking_spec.rb +62 -0
  291. data/spec/lib/landable/traffic_spec.rb +45 -0
  292. data/spec/models/landable/access_token_spec.rb +13 -0
  293. data/spec/models/landable/asset_spec.rb +48 -0
  294. data/spec/models/landable/directory_spec.rb +36 -0
  295. data/spec/models/landable/page/errors_spec.rb +30 -0
  296. data/spec/models/landable/page_revision_spec.rb +75 -0
  297. data/spec/models/landable/page_spec.rb +377 -0
  298. data/spec/models/landable/template_spec.rb +47 -0
  299. data/spec/models/landable/theme_spec.rb +8 -0
  300. data/spec/responders/page_render_responder_spec.rb +43 -0
  301. data/spec/routing/public_page_route_spec.rb +36 -0
  302. data/spec/services/landable/authentication_service_spec.rb +61 -0
  303. data/spec/services/landable/render_service_spec.rb +103 -0
  304. data/spec/services/landable/tidy_service_spec.rb +157 -0
  305. data/spec/spec_helper.rb +38 -0
  306. data/spec/support/behaviors.rb +107 -0
  307. data/spec/support/carrier_wave.rb +17 -0
  308. data/spec/support/categories.yml +2 -0
  309. data/spec/support/helpers.rb +22 -0
  310. metadata +795 -0
@@ -0,0 +1,37 @@
1
+ {
2
+ "title": "Theme",
3
+ "description": "A landable theme contains a Liquid template which can be used as a layout wrapping its pages' bodies",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["id", "name", "description", "body"],
7
+
8
+ "properties": {
9
+ "id": {
10
+ "$ref": "uuid.json"
11
+ },
12
+
13
+ "name": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ },
17
+
18
+ "description": {
19
+ "type": "string",
20
+ "minLength": 1
21
+ },
22
+
23
+ "body": {
24
+ "type": ["string", "null"],
25
+ "minLength": 0
26
+ },
27
+
28
+ "thumbnail_url": {
29
+ "type": ["string", "null"],
30
+ "minLength": 0
31
+ },
32
+
33
+ "editable": {
34
+ "type": "boolean"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "title": "UUID",
3
+ "description": "Unique identifier type used for all exposed resources",
4
+ "type": "string",
5
+ "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
6
+ }
@@ -0,0 +1,84 @@
1
+ @api @no-api-auth
2
+ Feature: Access Tokens API
3
+
4
+ Scenario: Responding with a fresh access token
5
+ Given an author "someone"
6
+ And "someone" has an unexpired access token
7
+ When I POST to "/api/access_tokens" with:
8
+ """
9
+ { "access_token": { "username": "someone", "password": "anything" } }
10
+ """
11
+ Then the response status should be 201 "Created"
12
+ And there should be 2 access tokens in the database
13
+
14
+ Scenario: Invalid username or password
15
+ When I POST to "/api/access_tokens" with:
16
+ """
17
+ { "access_token": { "username": "anything", "password": "fail" } }
18
+ """
19
+ Then the response status should be 401 "Not Authorized"
20
+ And the response body should be empty
21
+
22
+ Scenario: Creating an author if none yet exists
23
+ Given there are no authors in the database
24
+ When I POST to "/api/access_tokens" with:
25
+ """
26
+ { "access_token": { "username": "someone", "password": "anything" } }
27
+ """
28
+ Then an author "someone" should exist
29
+ And the author "someone" should have 1 access token
30
+
31
+ Scenario: Reusing a pre-existing author record
32
+ Given an author "someone"
33
+ When I POST to "/api/access_tokens" with:
34
+ """
35
+ { "access_token": { "username": "someone", "password": "anything" } }
36
+ """
37
+ Then there should be 1 author in the database
38
+ And the author "someone" should have 1 access token
39
+
40
+ Scenario: Retrieving my own fresh token
41
+ Given my API requests include a valid access token
42
+ When I GET "/api/access_tokens/{{@current_access_token.id}}"
43
+ Then the response status should be 200 "OK"
44
+
45
+ Scenario: Retrieving my expired token (while authenticating with a fresh one)
46
+ Given my API requests include a valid access token
47
+ And I also have an older, expired access token
48
+ When I GET "/api/access_tokens/{{@expired_access_token.id}}"
49
+ Then the response status should be 404 "Not Found"
50
+
51
+ Scenario: Retrieving someone else's token
52
+ Given my API requests include a valid access token
53
+ And there is another author's access token in the database
54
+ When I GET "/api/access_tokens/{{@foreign_access_token.id}}"
55
+ Then the response status should be 404 "Not Found"
56
+
57
+ Scenario: Refreshing an active token
58
+ Given my API requests include a valid access token
59
+ But my access token will expire in 2 minutes
60
+ When I PUT to "/api/access_tokens/{{@current_access_token.id}}"
61
+ Then my access token should not expire for at least 2 hours
62
+
63
+ Scenario: Refreshing an expired token
64
+ Given my API requests include a valid access token
65
+ But my access token expired 2 minutes ago
66
+ When I PUT to "/api/access_tokens/{{@current_access_token.id}}"
67
+ Then the response status should be 401 "Not Authorized"
68
+
69
+ Scenario: Deleting your own access token
70
+ Given my API requests include a valid access token
71
+ When I DELETE "/api/access_tokens/{{@current_access_token.id}}"
72
+ Then the response status should be 204 "No Content"
73
+ And there should be 0 access tokens in the database
74
+
75
+ Scenario: Deleting someone else's access token
76
+ Given my API requests include a valid access token
77
+ And there is another author's access token in the database
78
+ When I DELETE "/api/access_tokens/{{@foreign_access_token.id}}"
79
+ Then the response status should be 401 "Not Authorized"
80
+
81
+ Scenario: Deleting a non-existent token is 401, not 404
82
+ Given my API requests include a valid access token
83
+ When I DELETE "/api/access_tokens/{{random_uuid}}"
84
+ Then the response status should be 401 "Not Authorized"
@@ -0,0 +1,46 @@
1
+ @api
2
+ Feature: Asset management API
3
+
4
+ @no-api-auth @allow-rescue
5
+ Scenario Outline: Authentication required
6
+ When I <verb> "<path>"
7
+ Then the response should be 401 "Not Authorized"
8
+ When I repeat the request with a valid access token
9
+ Then the response should not be 401 "Not Authorized"
10
+
11
+ Examples:
12
+ | verb | path |
13
+ | GET | /api/assets |
14
+ | GET | /api/assets/1 |
15
+ | POST | /api/assets |
16
+
17
+ Scenario: Getting all assets
18
+ Given 3 assets
19
+ When I GET "/api/assets"
20
+ Then the response should contain 3 "assets"
21
+
22
+ Scenario: Getting multiple assets by ID
23
+ Given 3 assets
24
+ When I GET "/api/assets?ids[]={{@assets[0].id}}&ids[]={{@assets[1].id}}"
25
+ Then the response should contain 2 "assets"
26
+
27
+ Scenario: Searching by asset name
28
+ Given 2 assets named "panda" and "disclaimer"
29
+ When I GET "/api/assets?search[name]=p"
30
+ Then the response should contain 1 "assets"
31
+ And the JSON at "assets/0/name" should be "panda"
32
+
33
+ Scenario: Uploading a new asset
34
+ When I POST an asset to "/api/assets"
35
+ Then the response should be 201 "Created"
36
+ And the response should contain an "asset"
37
+ When I follow the "Location" header
38
+ Then the response should contain the same "asset"
39
+
40
+ Scenario: Uploading a pre-existing asset, based on SHA
41
+ Given an asset
42
+ When I POST that asset to "/api/assets" again
43
+ Then the response should be 301 "Moved Permanently"
44
+ And the response body should be empty
45
+ When I follow the "Location" header
46
+ Then the response should contain the original "asset"
@@ -0,0 +1,25 @@
1
+ @allow-rescue
2
+ Feature: Cross-Origin Support
3
+
4
+ Scenario Outline: Only supported from declared origins, within the API namespace
5
+ When I request CORS from "<path>" with:
6
+ | origin | method |
7
+ | <origin> | <method> |
8
+ Then the response should be <code>
9
+
10
+ Examples:
11
+ | path | origin | method | code |
12
+ | /priority | http://cors.test | PUT | 404 "Not Found" |
13
+ | /api/pages | http://cors.test | PUT | 200 "OK" |
14
+ | /api/pages | http://anything.else | PUT | 404 "Not Found" |
15
+
16
+ Scenario: Response headers to successful CORS OPTIONS requests
17
+ When I request CORS from "/api/pages" with:
18
+ | origin | method |
19
+ | http://cors.test | PUT |
20
+ Then the response should be 200 "OK"
21
+ And the response headers should include:
22
+ | header | value |
23
+ | Access-Control-Allow-Origin | http://cors.test |
24
+ | Access-Control-Allow-Methods | GET, POST, PUT, PATCH, DELETE |
25
+
@@ -0,0 +1,42 @@
1
+ @api
2
+ Feature: Pages API
3
+
4
+ Scenario: Loading multiple pages
5
+ Given 2 pages
6
+ When I GET "/api/pages?ids[]={{@pages[0].id}}&ids[]={{@pages[1].id}}"
7
+ Then the response should be 200 "OK"
8
+ And the response should contain 2 "pages"
9
+
10
+ Scenario: Loading a single page
11
+ Given a page
12
+ When I GET "/api/pages/{{@page.id}}"
13
+ Then the response should be 200 "OK"
14
+ And the response should contain a "page"
15
+
16
+ Scenario: Create a new page
17
+ When I POST to "/api/pages" with:
18
+ """
19
+ {
20
+ "path": "/page",
21
+ "body": "<HTML>BODY</HTML"
22
+ }
23
+ """
24
+ Then the response should be 201 "Created"
25
+ And the JSON at "page/path" should be "/page"
26
+
27
+ Scenario: Update a page
28
+ Given a page
29
+ When I PATCH "/api/pages/{{@page.id}}":
30
+ """
31
+ { "page": { "title": "Updated page" } }
32
+ """
33
+ Then the response should be 200 "OK"
34
+ And the JSON at "page/title" should be "Updated page"
35
+
36
+ Scenario: Publish a page
37
+ Given a page
38
+ When I POST "/api/pages/{{@page.id}}/publish"
39
+ Then the response should be 201 "Created"
40
+ And the JSON at "page/published_revision_id" should be a page revision's ID
41
+ When I GET "/api/page_revisions/{{@revision.id}}"
42
+ Then the response should be 200 "OK"
@@ -0,0 +1,16 @@
1
+ @api
2
+ Feature: Preview Page
3
+
4
+ Scenario: Preview a page
5
+ Given I accept JSON
6
+ When I POST "/api/pages/preview":
7
+ """
8
+ {
9
+ "page": {
10
+ "path": "/page/path",
11
+ "body": "This is just a preview!",
12
+ "status_code": "200"
13
+ }
14
+ }
15
+ """
16
+ Then the response should be 200 "OK"
@@ -0,0 +1,33 @@
1
+ @api
2
+ Feature: Templates API
3
+ Scenario: List all templates
4
+ Given 3 templates
5
+ When I GET "/api/templates"
6
+ Then the response status should be 200
7
+ And the response should contain 3 "templates"
8
+
9
+ Scenario: Create a new template
10
+ When I POST "/api/templates":
11
+ """
12
+ {
13
+ "template": {
14
+ "name": "A template name!",
15
+ "description": "A beautiful template",
16
+ "body": "<div>body</div>",
17
+ "thumbnail_url": "http://foo/bar.jpg"
18
+ }
19
+ }
20
+ """
21
+ Then the response should be 201 "Created"
22
+ And the JSON at "template/name" should be "A template name!"
23
+ When I follow the "Location" header
24
+ Then the JSON at "template/name" should be "A template name!"
25
+
26
+ Scenario: Update a template
27
+ Given a template
28
+ When I PATCH "/api/templates/{{@template.id}}":
29
+ """
30
+ { "template": { "name": "New day new name" } }
31
+ """
32
+ Then the response should be 200 "OK"
33
+ And the JSON at "template/name" should be "New day new name"
@@ -0,0 +1,33 @@
1
+ @api
2
+ Feature: Themes API
3
+ Scenario: List all themes
4
+ Given 3 themes
5
+ When I GET "/api/themes"
6
+ Then the response status should be 200
7
+ And the response should contain 3 "themes"
8
+
9
+ Scenario: Create a new theme
10
+ When I POST "/api/themes":
11
+ """
12
+ {
13
+ "theme": {
14
+ "name": "A theme name!",
15
+ "description": "A beautiful theme",
16
+ "body": "{{ body }}",
17
+ "thumbnail_url": "http://foo/bar.jpg"
18
+ }
19
+ }
20
+ """
21
+ Then the response should be 201 "Created"
22
+ And the JSON at "theme/name" should be "A theme name!"
23
+ When I follow the "Location" header
24
+ Then the JSON at "theme/name" should be "A theme name!"
25
+
26
+ Scenario: Update a theme
27
+ Given a theme
28
+ When I PATCH "/api/themes/{{@theme.id}}":
29
+ """
30
+ { "theme": { "name": "New day new name" } }
31
+ """
32
+ Then the response should be 200 "OK"
33
+ And the JSON at "theme/name" should be "New day new name"
@@ -0,0 +1,35 @@
1
+ Feature: Page body via `body` variable
2
+ Scenario: Theme accesses rendered page body via `body` variable
3
+ Given a theme with the body:
4
+ """
5
+ <div class="container">{{body}}</div>
6
+ """
7
+ When this page is rendered:
8
+ """
9
+ <h1>Hi mom!</h1>
10
+ """
11
+ Then the rendered content should be:
12
+ """
13
+ <div class="container"><h1>Hi mom!</h1></div>
14
+ """
15
+
16
+ Scenario: Rendering a page that attempts to inject Liquid markup into its theme
17
+ Given a theme with the body "{{body}}"
18
+ When this page is rendered:
19
+ """
20
+ {% raw %}{{body}}{% endraw %}
21
+ """
22
+ Then the rendered content should be:
23
+ """
24
+ {{body}}
25
+ """
26
+
27
+ Scenario: Page rendering does not provide a `body` variable
28
+ When this page is rendered:
29
+ """
30
+ <div>{{body}}</div>
31
+ """
32
+ Then the rendered content should be:
33
+ """
34
+ <div></div>
35
+ """
@@ -0,0 +1,54 @@
1
+ Feature: Liquid drop for categories
2
+ As a content author
3
+ I want to be able to create category lists
4
+ And I want to be able to create lists of pages
5
+
6
+ Background:
7
+ Given a page under test
8
+
9
+ Scenario: Category list
10
+ Given a "published" page with title "Title 1" and category "seo"
11
+ Given a "published" page with title "Title 1" and category "affiliates"
12
+ Given a "published" page with title "Title 1" and category "traditional"
13
+ Given a "published" page with title "Title 1" and category "traditional"
14
+ Given a "unpublished" page with title "Title 1" and category "traditional"
15
+ When this page is rendered:
16
+ """
17
+ {% for category in categories %}{{ category.name }} ({{ category.pages.size }})
18
+ {% endfor %}
19
+ """
20
+ Then the rendered content should be:
21
+ """
22
+ Uncategorized (0)
23
+ Affiliates (1)
24
+ PPC (0)
25
+ SEO (1)
26
+ Social (0)
27
+ Email (0)
28
+ Traditional (2)
29
+ """
30
+
31
+ Scenario: Category when there are no published pages
32
+ Given a "unpublished" page with title "Title 1" and category "seo"
33
+ When this page is rendered:
34
+ """
35
+ count: {{ categories.seo.pages.size }}
36
+ {% for page in categories.seo.pages %}{{page.title}}{% endfor %}
37
+ """
38
+ Then the rendered content should be:
39
+ """
40
+ count: 0
41
+ """
42
+
43
+ Scenario: Category proxy when there is one published page
44
+ Given a "published" page with title "Title 1" and category "seo"
45
+ When this page is rendered:
46
+ """
47
+ count: {{ categories.seo.pages.size }}
48
+ {% for page in categories.seo.pages %}{{page.title}}{% endfor %}
49
+ """
50
+ Then the rendered content should be:
51
+ """
52
+ count: 1
53
+ Title 1
54
+ """
@@ -0,0 +1,168 @@
1
+ # This is totally a unit test, but it's much more informative to read
2
+ # this in cuke form.
3
+ Feature: Liquid Tags
4
+ A number of custom liquid tags are made available to the page and theme bodies,
5
+ enabling generation of HTML tags, referencing assets, etc.
6
+
7
+ Background:
8
+ Given the asset URI prefix is "https://landable.dev/_assets"
9
+ And a page under test
10
+ And these assets:
11
+ | name | description |
12
+ | panda | Baz! |
13
+ | cthulhu | Wisconsin Disclosures |
14
+ | small | Site Favicon |
15
+
16
+
17
+ Scenario: title_tag
18
+ Given the page's body is "{% title_tag %}"
19
+ Then the rendered content should be:
20
+ """
21
+ <title>Page Under Test</title>
22
+ """
23
+
24
+ Scenario: meta_tags
25
+ Given the page's body is "{% meta_tags %}"
26
+ And the page's meta tags are:
27
+ | name | content |
28
+ | robots | noindex,nofollow |
29
+ | keywords | momoney,moproblems |
30
+ Then the rendered content should be:
31
+ """
32
+ <meta content="noindex,nofollow" name="robots" />
33
+ <meta content="momoney,moproblems" name="keywords" />
34
+ """
35
+
36
+ Scenario: head_content
37
+ Given the page's body is "{% head_content %}"
38
+ And the page's head tag is "<head lang='en'><meta test='text'>"
39
+ Then the rendered content should be:
40
+ """
41
+ <head lang='en'><meta test='text'>
42
+ """
43
+
44
+ Scenario: head
45
+ Given the page's body is "{% head %}"
46
+ And the page's head tag is "<head lang='en'><meta test='text'>"
47
+ And the page's meta tags are:
48
+ | name | content |
49
+ | robots | noindex,nofollow |
50
+ | keywords | momoney,moproblems |
51
+ Then the rendered content should be:
52
+ """
53
+ <title>Page Under Test</title>
54
+ <meta content="noindex,nofollow" name="robots" />
55
+ <meta content="momoney,moproblems" name="keywords" />
56
+ <head lang='en'><meta test='text'>
57
+ """
58
+
59
+ Scenario: body
60
+ Given the page's body is "{% body %}"
61
+ And the page's body is "<div>Page body</div>"
62
+ Then the rendered content should be:
63
+ """
64
+ <div>Page body</div>
65
+ """
66
+
67
+ Scenario: body with nested liquid tag
68
+ Given the page's body is "{% body %}"
69
+ And the page's body is "<div>Page body</div>{% meta_tags %}"
70
+ And the page's meta tags are:
71
+ | name | content |
72
+ | robots | noindex,nofollow |
73
+ | keywords | momoney,moproblems |
74
+ Then the rendered content should be:
75
+ """
76
+ <div>Page body</div><meta content="noindex,nofollow" name="robots" />
77
+ <meta content="momoney,moproblems" name="keywords" />
78
+ """
79
+
80
+ Scenario: img_tag
81
+ Given the page's body is "{% img_tag panda %}"
82
+ Then the rendered content should be:
83
+ """
84
+ <img alt="Baz!" src="https://landable.dev/_assets//uploads/panda.png" />
85
+ """
86
+
87
+ Scenario: asset_url and asset_description
88
+ Given the page's body is:
89
+ """
90
+ <a href="{% asset_url cthulhu %}" title="{% asset_description cthulhu %}">Disclosures</a>
91
+ """
92
+ Then the rendered content should be:
93
+ """
94
+ <a href="https://landable.dev/_assets//uploads/cthulhu.jpg" title="Wisconsin Disclosures">Disclosures</a>
95
+ """
96
+
97
+ Scenario: Referencing a template
98
+ Given the page's body is:
99
+ """
100
+ <div>{% template bar %}</div>
101
+ """
102
+ And the template "bar" with body "<span>some stuff</span>"
103
+ Then the rendered content should be:
104
+ """
105
+ <div><span>some stuff</span></div>
106
+ """
107
+
108
+ Scenario: Referencing file backed partials
109
+ Given the page's body is:
110
+ """
111
+ <div>{% template partials_foobazz %}</div>
112
+ """
113
+ And the template is a filed backed partial
114
+ Then the rendered content should be:
115
+ """
116
+ <div><div class='content'>some stuff</div></div>
117
+ """
118
+
119
+ Scenario: Referencing a template with variables
120
+ Given the page's body is:
121
+ """
122
+ <div>{% template foobar body: "seven" footer: "the end" %}</div>
123
+ """
124
+ And the template "foobar" with the body:
125
+ """
126
+ <span>{{ body | default: "eight" }}</span>
127
+ <footer>{{ footer }}</footer>
128
+ """
129
+ Then the rendered content should be:
130
+ """
131
+ <div><span>seven</span>
132
+ <footer>the end</footer></div>
133
+ """
134
+
135
+ Scenario: Referencing a template with variable defaults
136
+ Given the page's body is:
137
+ """
138
+ <div>{% template bar %}</div>
139
+ """
140
+ And the template "bar" with the body:
141
+ """
142
+ <span>{{ body | default: "eight" }}</span>
143
+ """
144
+ Then the rendered content should be:
145
+ """
146
+ <div><span>eight</span></div>
147
+ """
148
+
149
+ Scenario: Referencing a template that doesn't exist
150
+ Given the page's body is:
151
+ """
152
+ <div>{% template fubu %}</div>
153
+ """
154
+ Then the rendered content should be:
155
+ """
156
+ <div><!-- render error: missing template "fubu" --></div>
157
+ """
158
+
159
+ Scenario: App asset tags
160
+ Given the page's body is:
161
+ """
162
+ {% stylesheet_link_tag application %}
163
+ {% javascript_include_tag application %}
164
+ {% image_tag foo.jpg %}
165
+ {% img_tag foo.jpg %}
166
+ {% image_tag panda %}
167
+ """
168
+ Then the rendered body should be the correct assets