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