landable 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/MIT-LICENSE +20 -0
- data/README.md +56 -0
- data/Rakefile +22 -0
- data/app/controllers/concerns/landable/variables_concern.rb +38 -0
- data/app/controllers/landable/api/access_tokens_controller.rb +48 -0
- data/app/controllers/landable/api/assets_controller.rb +55 -0
- data/app/controllers/landable/api/categories_controller.rb +13 -0
- data/app/controllers/landable/api/directories_controller.rb +19 -0
- data/app/controllers/landable/api/page_revisions_controller.rb +38 -0
- data/app/controllers/landable/api/pages_controller.rb +96 -0
- data/app/controllers/landable/api/templates_controller.rb +33 -0
- data/app/controllers/landable/api/themes_controller.rb +57 -0
- data/app/controllers/landable/api_controller.rb +75 -0
- data/app/controllers/landable/application_controller.rb +4 -0
- data/app/controllers/landable/public/pages_controller.rb +25 -0
- data/app/controllers/landable/public/preview/page_revisions_controller.rb +16 -0
- data/app/controllers/landable/public/preview/pages_controller.rb +16 -0
- data/app/controllers/landable/public/sitemap_controller.rb +18 -0
- data/app/decorators/landable/null_page_decorator.rb +26 -0
- data/app/decorators/landable/page_decorator.rb +40 -0
- data/app/helpers/landable/application_helper.rb +13 -0
- data/app/helpers/landable/pages_helper.rb +20 -0
- data/app/models/concerns/landable/has_assets.rb +74 -0
- data/app/models/concerns/landable/table_name.rb +12 -0
- data/app/models/concerns/landable/traffic/table_name.rb +14 -0
- data/app/models/landable/access_token.rb +20 -0
- data/app/models/landable/asset.rb +63 -0
- data/app/models/landable/asset_search_engine.rb +20 -0
- data/app/models/landable/author.rb +12 -0
- data/app/models/landable/category.rb +27 -0
- data/app/models/landable/directory.rb +23 -0
- data/app/models/landable/page.rb +259 -0
- data/app/models/landable/page_revision.rb +74 -0
- data/app/models/landable/page_search_engine.rb +20 -0
- data/app/models/landable/search_engine.rb +39 -0
- data/app/models/landable/template.rb +24 -0
- data/app/models/landable/theme.rb +20 -0
- data/app/models/landable/traffic/access.rb +11 -0
- data/app/models/landable/traffic/ad_group.rb +11 -0
- data/app/models/landable/traffic/ad_type.rb +11 -0
- data/app/models/landable/traffic/attribution.rb +38 -0
- data/app/models/landable/traffic/bid_match_type.rb +11 -0
- data/app/models/landable/traffic/browser.rb +11 -0
- data/app/models/landable/traffic/campaign.rb +11 -0
- data/app/models/landable/traffic/city.rb +11 -0
- data/app/models/landable/traffic/content.rb +11 -0
- data/app/models/landable/traffic/cookie.rb +16 -0
- data/app/models/landable/traffic/country.rb +11 -0
- data/app/models/landable/traffic/creative.rb +11 -0
- data/app/models/landable/traffic/device.rb +11 -0
- data/app/models/landable/traffic/device_type.rb +11 -0
- data/app/models/landable/traffic/domain.rb +12 -0
- data/app/models/landable/traffic/event.rb +12 -0
- data/app/models/landable/traffic/event_type.rb +11 -0
- data/app/models/landable/traffic/experiment.rb +11 -0
- data/app/models/landable/traffic/http_method.rb +11 -0
- data/app/models/landable/traffic/ip_address.rb +11 -0
- data/app/models/landable/traffic/ip_lookup.rb +12 -0
- data/app/models/landable/traffic/keyword.rb +11 -0
- data/app/models/landable/traffic/location.rb +11 -0
- data/app/models/landable/traffic/match_type.rb +11 -0
- data/app/models/landable/traffic/medium.rb +11 -0
- data/app/models/landable/traffic/mime_type.rb +11 -0
- data/app/models/landable/traffic/network.rb +11 -0
- data/app/models/landable/traffic/owner.rb +10 -0
- data/app/models/landable/traffic/ownership.rb +10 -0
- data/app/models/landable/traffic/page_view.rb +24 -0
- data/app/models/landable/traffic/path.rb +13 -0
- data/app/models/landable/traffic/placement.rb +11 -0
- data/app/models/landable/traffic/platform.rb +11 -0
- data/app/models/landable/traffic/position.rb +11 -0
- data/app/models/landable/traffic/query_string.rb +12 -0
- data/app/models/landable/traffic/referer.rb +11 -0
- data/app/models/landable/traffic/region.rb +11 -0
- data/app/models/landable/traffic/search_term.rb +11 -0
- data/app/models/landable/traffic/source.rb +11 -0
- data/app/models/landable/traffic/target.rb +11 -0
- data/app/models/landable/traffic/user_agent.rb +28 -0
- data/app/models/landable/traffic/user_agent_type.rb +11 -0
- data/app/models/landable/traffic/visit.rb +15 -0
- data/app/models/landable/traffic/visitor.rb +13 -0
- data/app/responders/landable/api_responder.rb +76 -0
- data/app/responders/landable/page_render_responder.rb +15 -0
- data/app/serializers/landable/access_token_serializer.rb +6 -0
- data/app/serializers/landable/asset_serializer.rb +14 -0
- data/app/serializers/landable/author_serializer.rb +5 -0
- data/app/serializers/landable/category_serializer.rb +5 -0
- data/app/serializers/landable/directory_serializer.rb +8 -0
- data/app/serializers/landable/page_revision_serializer.rb +15 -0
- data/app/serializers/landable/page_serializer.rb +31 -0
- data/app/serializers/landable/template_serializer.rb +5 -0
- data/app/serializers/landable/theme_serializer.rb +7 -0
- data/app/services/landable/authentication_service.rb +44 -0
- data/app/services/landable/registration_service.rb +13 -0
- data/app/services/landable/render_service.rb +86 -0
- data/app/services/landable/tidy_service.rb +155 -0
- data/app/uploaders/landable/asset_uploader.rb +9 -0
- data/app/validators/path_validator.rb +12 -0
- data/app/validators/url_validator.rb +13 -0
- data/app/views/templates/preview.liquid +122 -0
- data/bin/rails +8 -0
- data/bin/redb +7 -0
- data/config.ru +7 -0
- data/config/cucumber.yml +5 -0
- data/config/routes.rb +62 -0
- data/db/migrate/20130510221424_create_landable_schema.rb +338 -0
- data/db/migrate/20130909182713_landable_pages__add_updated_by.rb +11 -0
- data/db/migrate/20130909182715_landable_page_revisions__break_out_snapshot.rb +72 -0
- data/db/migrate/20130909191153_landable_pages__add_lock_version.rb +5 -0
- data/db/migrate/20131002220041_file_based_themes.rb +12 -0
- data/db/migrate/20131008164204_create_head_tag_on_page.rb +19 -0
- data/db/migrate/20131008193544_drop_status_codes_model.rb +44 -0
- data/db/migrate/20131028145652_add_traffic_schema.rb +276 -0
- data/db/migrate/20131101213623_add_dnt_column_to_visits.rb +7 -0
- data/db/migrate/20131104224120_add_meta_on_events.rb +7 -0
- data/db/migrate/20131106185946_add_index_on_page_revisions_path.rb +6 -0
- data/db/migrate/20131106193021_page_revisisons__path_status_code_index.rb +9 -0
- data/db/migrate/20131108212501_traffic_owner_ids_are_serials.rb +20 -0
- data/db/migrate/20131121150902_add_attribution_id_to_unique_index.rb +10 -0
- data/db/migrate/20131216214027_drop_browser_screenshot_tables.rb +6 -0
- data/db/migrate/20140128170659_file_backed_templates.rb +8 -0
- data/db/migrate/20140205193757_fix_status_codes.rb +24 -0
- data/db/migrate/20140206211322_add_response_time_to_traffic_page_views.rb +7 -0
- data/db/migrate/20140220170324_add_slug_to_categories.rb +14 -0
- data/db/migrate/20140220174630_add_abstract_and_hero_asset_to_pages_and_page_revisions.rb +8 -0
- data/db/migrate/20140224205516_rename_traffic_schema.rb +40 -0
- data/db/pgtap/pgtap.sql +9034 -0
- data/db/test/landable.access_tokens.sql +13 -0
- data/db/test/landable.assets.sql +16 -0
- data/db/test/landable.authors.sql +22 -0
- data/db/test/landable.categories.sql +9 -0
- data/db/test/landable.page_revisions.sql +41 -0
- data/db/test/landable.pages.sql +19 -0
- data/db/test/landable.templates.sql +15 -0
- data/db/test/landable.themes.sql +25 -0
- data/doc/schema/access_token.json +22 -0
- data/doc/schema/asset.json +65 -0
- data/doc/schema/author.json +30 -0
- data/doc/schema/directory.json +24 -0
- data/doc/schema/page.json +95 -0
- data/doc/schema/page_revision.json +70 -0
- data/doc/schema/theme.json +37 -0
- data/doc/schema/uuid.json +6 -0
- data/features/api/access_tokens.feature +84 -0
- data/features/api/assets.feature +46 -0
- data/features/api/cors.feature +25 -0
- data/features/api/pages.feature +42 -0
- data/features/api/preview.feature +16 -0
- data/features/api/templates.feature +33 -0
- data/features/api/themes.feature +33 -0
- data/features/liquid/body.feature +35 -0
- data/features/liquid/drops/categories.feature +54 -0
- data/features/liquid/tags.feature +168 -0
- data/features/public/content_types.feature +17 -0
- data/features/public/publishing.feature +45 -0
- data/features/public/status_codes.feature +25 -0
- data/features/public/views.feature +17 -0
- data/features/step_definitions/asset_steps.rb +60 -0
- data/features/step_definitions/core_api_steps.rb +139 -0
- data/features/step_definitions/debug_steps.rb +3 -0
- data/features/step_definitions/factory_steps.rb +124 -0
- data/features/step_definitions/html_steps.rb +9 -0
- data/features/step_definitions/liquid_steps.rb +79 -0
- data/features/step_definitions/revision_steps.rb +5 -0
- data/features/step_definitions/theme_steps.rb +43 -0
- data/features/support/env.rb +66 -0
- data/features/support/usefulness.rb +13 -0
- data/landable.gemspec +54 -0
- data/lib/generators/landable/collection/collection_generator.rb +0 -0
- data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%.less +0 -0
- data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/mixins.less +0 -0
- data/lib/generators/landable/collection/templates/stylesheets/landable/%file_name%/variables.less +0 -0
- data/lib/generators/landable/component/component_generator.rb +0 -0
- data/lib/generators/landable/component/templates/javascripts/landable/%file_name%.less +0 -0
- data/lib/generators/landable/component/templates/stylesheets/landable/%file_name%.less +0 -0
- data/lib/generators/landable/install_generator.rb +19 -0
- data/lib/generators/landable/landable_generator.rb +22 -0
- data/lib/generators/templates/landable.rb +34 -0
- data/lib/landable.rb +30 -0
- data/lib/landable/configuration.rb +115 -0
- data/lib/landable/core_ext/ipaddr.rb +18 -0
- data/lib/landable/engine.rb +69 -0
- data/lib/landable/error.rb +16 -0
- data/lib/landable/inflections.rb +4 -0
- data/lib/landable/layout.rb +60 -0
- data/lib/landable/liquid.rb +27 -0
- data/lib/landable/liquid/asset_tags.rb +76 -0
- data/lib/landable/liquid/drops.rb +46 -0
- data/lib/landable/liquid/filters.rb +11 -0
- data/lib/landable/liquid/tags.rb +91 -0
- data/lib/landable/migration.rb +40 -0
- data/lib/landable/mime_types.rb +15 -0
- data/lib/landable/partial.rb +46 -0
- data/lib/landable/seeds.rb +36 -0
- data/lib/landable/traffic.rb +34 -0
- data/lib/landable/traffic/crawl_tracker.rb +9 -0
- data/lib/landable/traffic/noop_tracker.rb +8 -0
- data/lib/landable/traffic/ping_tracker.rb +9 -0
- data/lib/landable/traffic/scan_tracker.rb +9 -0
- data/lib/landable/traffic/scrape_tracker.rb +9 -0
- data/lib/landable/traffic/tracker.rb +283 -0
- data/lib/landable/traffic/user_tracker.rb +65 -0
- data/lib/landable/version.rb +10 -0
- data/lib/tasks/landable/cucumber.rake +67 -0
- data/lib/tasks/landable/data.rake +166 -0
- data/lib/tasks/landable/pgtap.rake +26 -0
- data/lib/tasks/landable/rdoc.rake +11 -0
- data/lib/tasks/landable/seed.rake +16 -0
- data/lib/tasks/landable/spec.rake +15 -0
- data/script/cucumber +10 -0
- data/spec/concerns/landable/has_assets_spec.rb +75 -0
- data/spec/concerns/landable/table_name_spec.rb +15 -0
- data/spec/concerns/landable/traffic/table_name_spec.rb +16 -0
- data/spec/controllers/concerns/landable/variables_concern_spec.rb +66 -0
- data/spec/controllers/landable/api/assets_controller_spec.rb +24 -0
- data/spec/controllers/landable/api/categories_controller_spec.rb +45 -0
- data/spec/controllers/landable/api/directories_controller_spec.rb +56 -0
- data/spec/controllers/landable/api/page_revisions_controller_spec.rb +29 -0
- data/spec/controllers/landable/api/pages_controller_spec.rb +271 -0
- data/spec/controllers/landable/api_controller_spec.rb +189 -0
- data/spec/controllers/public/preview/page_revisions_controller_spec.rb +41 -0
- data/spec/controllers/public/preview/pages_controller_spec.rb +36 -0
- data/spec/controllers/public/sitemap_controller_spec.rb +25 -0
- data/spec/decorators/page_decorator_spec.rb +90 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/foo.jpg +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +10 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/controllers/priority_controller.rb +7 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/priority.html.erb +18 -0
- data/spec/dummy/app/views/partials/_foobazz.html +1 -0
- data/spec/dummy/app/views/priority/show.html.erb +11 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +21 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +60 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/landable.rb +22 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/db/structure.sql +3736 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/asset.rb +29 -0
- data/spec/factories/authors.rb +12 -0
- data/spec/factories/category.rb +6 -0
- data/spec/factories/page_revision.rb +6 -0
- data/spec/factories/pages.rb +33 -0
- data/spec/factories/template.rb +13 -0
- data/spec/factories/theme.rb +14 -0
- data/spec/fixtures/assets/cthulhu.jpg +0 -0
- data/spec/fixtures/assets/panda.png +0 -0
- data/spec/fixtures/assets/sloth.png +0 -0
- data/spec/fixtures/assets/small.pdf +0 -0
- data/spec/helpers/pages_helper_spec.rb +35 -0
- data/spec/lib/landable/configuration_spec.rb +20 -0
- data/spec/lib/landable/layout_spec.rb +25 -0
- data/spec/lib/landable/liquid_spec.rb +24 -0
- data/spec/lib/landable/migration_spec.rb +51 -0
- data/spec/lib/landable/partial_spec.rb +84 -0
- data/spec/lib/landable/tracking_spec.rb +62 -0
- data/spec/lib/landable/traffic_spec.rb +45 -0
- data/spec/models/landable/access_token_spec.rb +13 -0
- data/spec/models/landable/asset_spec.rb +48 -0
- data/spec/models/landable/directory_spec.rb +36 -0
- data/spec/models/landable/page/errors_spec.rb +30 -0
- data/spec/models/landable/page_revision_spec.rb +75 -0
- data/spec/models/landable/page_spec.rb +377 -0
- data/spec/models/landable/template_spec.rb +47 -0
- data/spec/models/landable/theme_spec.rb +8 -0
- data/spec/responders/page_render_responder_spec.rb +43 -0
- data/spec/routing/public_page_route_spec.rb +36 -0
- data/spec/services/landable/authentication_service_spec.rb +61 -0
- data/spec/services/landable/render_service_spec.rb +103 -0
- data/spec/services/landable/tidy_service_spec.rb +157 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/behaviors.rb +107 -0
- data/spec/support/carrier_wave.rb +17 -0
- data/spec/support/categories.yml +2 -0
- data/spec/support/helpers.rb +22 -0
- 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,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
|