landable 1.7.0

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 (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