storytime 2.1.6 → 2.1.7
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 +5 -5
- data/.github/workflows/test.yml +54 -0
- data/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +414 -425
- data/Guardfile +1 -1
- data/app/assets/fonts/storytime-icons.eot +0 -0
- data/app/assets/fonts/storytime-icons.svg +23 -0
- data/app/assets/fonts/storytime-icons.ttf +0 -0
- data/app/assets/fonts/storytime-icons.woff +0 -0
- data/app/assets/javascripts/storytime/application.js +1 -8
- data/app/assets/javascripts/storytime/off_canvas.coffee +16 -0
- data/app/assets/javascripts/storytime/wysiwyg.js.coffee +9 -7
- data/app/assets/stylesheets/storytime/_buttons.scss +33 -0
- data/app/assets/stylesheets/storytime/_list-group.scss +1 -1
- data/app/assets/stylesheets/storytime/application.scss +14 -2
- data/app/assets/stylesheets/storytime/icons.scss +5 -5
- data/app/assets/stylesheets/storytime/leather/_buttons.scss +31 -0
- data/app/assets/stylesheets/storytime/leather/_devise.scss +72 -0
- data/app/assets/stylesheets/storytime/leather/_grid.scss +19 -0
- data/app/assets/stylesheets/storytime/leather/_list_groups.scss +33 -0
- data/app/assets/stylesheets/storytime/leather/_nav_menus.scss +125 -0
- data/app/assets/stylesheets/storytime/leather/_navbar_transparent.scss +23 -0
- data/app/assets/stylesheets/storytime/leather/_off_canvas.scss +109 -0
- data/app/assets/stylesheets/storytime/leather/_scroll_panels.scss +85 -0
- data/app/assets/stylesheets/storytime/leather/_toggle_columns.scss +23 -0
- data/app/assets/stylesheets/storytime/leather/_typography.scss +12 -0
- data/app/assets/stylesheets/storytime/leather/_utilities.scss +54 -0
- data/app/assets/stylesheets/storytime/leather/_variables.scss +8 -0
- data/app/assets/stylesheets/storytime/posts.scss +9 -1
- data/app/controllers/storytime/application_controller.rb +1 -1
- data/app/controllers/storytime/dashboard/blog_posts_controller.rb +5 -3
- data/app/controllers/storytime/dashboard/blogs_controller.rb +1 -1
- data/app/controllers/storytime/dashboard/media_controller.rb +4 -4
- data/app/controllers/storytime/dashboard/memberships_controller.rb +1 -1
- data/app/controllers/storytime/dashboard/pages_controller.rb +26 -6
- data/app/controllers/storytime/dashboard/posts_controller.rb +30 -5
- data/app/controllers/storytime/dashboard/subscriptions_controller.rb +1 -1
- data/app/controllers/storytime/pages_controller.rb +31 -1
- data/app/controllers/storytime/posts_controller.rb +1 -1
- data/app/controllers/storytime/subscriptions_controller.rb +5 -1
- data/app/helpers/storytime/application_helper.rb +0 -4
- data/app/models/concerns/storytime/post_featured_images.rb +2 -2
- data/app/models/concerns/storytime/post_partial_inheritance.rb +2 -2
- data/app/models/storytime/autosave.rb +1 -1
- data/app/models/storytime/comment.rb +1 -1
- data/app/models/storytime/media.rb +1 -1
- data/app/models/storytime/membership.rb +2 -2
- data/app/models/storytime/post.rb +7 -3
- data/app/models/storytime/site.rb +4 -6
- data/app/models/storytime/snippet.rb +12 -1
- data/app/models/storytime/subscription.rb +2 -2
- data/app/models/storytime/version.rb +1 -1
- data/app/policies/storytime/post_policy.rb +1 -1
- data/app/views/storytime/dashboard/_navigation.html.erb +0 -9
- data/app/views/storytime/dashboard/blog_posts/_form.html.erb +5 -1
- data/app/views/storytime/dashboard/blogs/edit.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/blogs/index.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/blogs/new.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/media/_gallery.html.erb +1 -1
- data/app/views/storytime/dashboard/media/_modal.html.erb +2 -2
- data/app/views/storytime/dashboard/media/show.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/memberships/_edit.html.erb +1 -1
- data/app/views/storytime/dashboard/memberships/_index.html.erb +3 -3
- data/app/views/storytime/dashboard/memberships/_new.html.erb +1 -1
- data/app/views/storytime/dashboard/memberships/edit.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/memberships/index.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/memberships/new.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/memberships/save.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/pages/_form.html.erb +1 -1
- data/app/views/storytime/dashboard/posts/_directory.html.erb +18 -0
- data/app/views/storytime/dashboard/posts/_form.html.erb +1 -1
- data/app/views/storytime/dashboard/posts/_list.html.erb +2 -26
- data/app/views/storytime/dashboard/posts/_new_button.html.erb +1 -1
- data/app/views/storytime/dashboard/posts/_post.html.erb +28 -0
- data/app/views/storytime/dashboard/posts/_sorts.html.erb +21 -0
- data/app/views/storytime/dashboard/posts/index.html.erb +5 -3
- data/app/views/storytime/dashboard/roles/_form.html.erb +1 -1
- data/app/views/storytime/dashboard/roles/edit.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/sites/_form.html.erb +1 -1
- data/app/views/storytime/dashboard/sites/site.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/snippets/_index.html.erb +1 -1
- data/app/views/storytime/dashboard/snippets/edit.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/snippets/index.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/snippets/new.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/subscriptions/_form.html.erb +1 -1
- data/app/views/storytime/dashboard/subscriptions/_index.html.erb +2 -2
- data/app/views/storytime/dashboard/subscriptions/form.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/subscriptions/index.json.jbuilder +1 -1
- data/app/views/storytime/dashboard/versions/_versions_info.html.erb +1 -1
- data/app/views/storytime/posts/show.html.erb +6 -0
- data/app/views/storytime/sites/_google_analytics_code.html.erb +5 -8
- data/config/initializers/assets.rb +2 -1
- data/config/initializers/friendly_id.rb +1 -1
- data/config/initializers/url_for_patch.rb +19 -8
- data/config/locales/devise.zh-CN.yml +59 -0
- data/config/locales/kaminari.zh-CN.yml +17 -0
- data/config/locales/simple_form.zh-CN.yml +26 -0
- data/config/locales/zh-CN.yml +141 -0
- data/config/routes.rb +2 -4
- data/db/migrate/20140501174341_create_storytime_posts.rb +1 -1
- data/db/migrate/20140509191309_create_friendly_id_slugs.rb +1 -1
- data/db/migrate/20140511200849_create_storytime_media.rb +1 -1
- data/db/migrate/20140513161233_create_storytime_sites.rb +1 -1
- data/db/migrate/20140514200234_create_storytime_tags.rb +1 -1
- data/db/migrate/20140514200304_create_storytime_taggings.rb +1 -1
- data/db/migrate/20140516141252_create_storytime_versions.rb +1 -1
- data/db/migrate/20140521190606_create_storytime_roles.rb +1 -1
- data/db/migrate/20140521191048_add_storytime_role_id_to_users.rb +1 -1
- data/db/migrate/20140521191728_create_storytime_permissions.rb +1 -1
- data/db/migrate/20140521191744_create_storytime_actions.rb +1 -1
- data/db/migrate/20140813014447_create_storytime_comments.rb +1 -1
- data/db/migrate/20140813130534_add_storytime_name_to_users.rb +1 -1
- data/db/migrate/20140916183056_create_storytime_autosaves.rb +1 -1
- data/db/migrate/20141020213343_add_secondary_media_id_to_storytime_post.rb +1 -1
- data/db/migrate/20141021073356_create_storytime_snippets.rb +1 -1
- data/db/migrate/20141111164439_create_storytime_subscriptions.rb +1 -1
- data/db/migrate/20150122200805_add_title_and_content_index_to_storytime_post.rb +1 -1
- data/db/migrate/20150128185746_seed_new_actions_and_permissions.rb +1 -1
- data/db/migrate/20150129215308_add_site_id_to_storytime_subscription.rb +1 -1
- data/db/migrate/20150206201847_add_site_id_to_storytime_post.rb +1 -1
- data/db/migrate/20150206201919_add_site_id_to_storytime_snippet.rb +1 -1
- data/db/migrate/20150206201931_add_site_id_to_storytime_tag.rb +1 -1
- data/db/migrate/20150206205256_add_notification_fields_to_storytime_post.rb +1 -1
- data/db/migrate/20150216211257_add_subdomain_to_storytime_sites.rb +1 -1
- data/db/migrate/20150216225045_add_site_to_storytime_media.rb +1 -1
- data/db/migrate/20150219210528_remove_root_page_content_from_storytime_sites.rb +1 -1
- data/db/migrate/20150220184902_add_blog_id_to_posts.rb +1 -1
- data/db/migrate/20150224192138_add_homepage_path_to_storytime_sites.rb +1 -1
- data/db/migrate/20150224193151_add_subscription_email_from_to_storytime_sites.rb +1 -1
- data/db/migrate/20150224193551_add_layout_to_storytime_sites.rb +1 -1
- data/db/migrate/20150224194559_add_disqus_forum_shortname_to_storytime_sites.rb +1 -1
- data/db/migrate/20150224212453_remove_homepage_path_from_storytime_sites.rb +1 -1
- data/db/migrate/20150225143516_add_site_id_to_storytime_autosaves.rb +1 -1
- data/db/migrate/20150225143826_add_site_id_to_storytime_comments.rb +1 -1
- data/db/migrate/20150225145119_add_site_id_to_storytime_versions.rb +1 -1
- data/db/migrate/20150225145316_add_site_id_to_storytime_taggings.rb +1 -1
- data/db/migrate/20150225145608_update_storytime_site_id_columns.rb +1 -1
- data/db/migrate/20150225164232_add_site_id_to_storytime_permissions.rb +1 -1
- data/db/migrate/20150225212917_create_storytime_memberships.rb +1 -1
- data/db/migrate/20150225213535_create_memberships_for_storytime_users.rb +1 -1
- data/db/migrate/20150226201739_add_custom_domain_to_storytime_sites.rb +1 -1
- data/db/migrate/20150302171500_add_site_id_to_storytime_media.rb +1 -1
- data/db/migrate/20150302171722_set_site_layout.rb +1 -1
- data/db/migrate/20150302185138_remove_storytime_role_id_from_users.rb +1 -1
- data/db/migrate/20150302192525_transfer_posts_to_blogs.rb +1 -1
- data/db/migrate/20150302192759_seed_permissions.rb +1 -1
- data/db/migrate/20150331162329_add_discourse_name_to_storytime_sites.rb +1 -1
- data/db/migrate/20150402161427_remove_subdomain_from_storytime_site.rb +1 -1
- data/db/migrate/20150520181115_create_storytime_navigations.rb +1 -1
- data/db/migrate/20150520185227_create_storytime_links.rb +1 -1
- data/db/migrate/20150520190700_add_position_to_storytime_links.rb +1 -1
- data/db/migrate/20150529192058_add_url_to_storytime_links.rb +1 -1
- data/db/migrate/20260408001637_add_canonical_url_to_storytime_posts.rb +5 -0
- data/db/migrate/20260701000000_sanitize_existing_storytime_snippets.rb +22 -0
- data/lib/storytime/cli/install.rb +2 -20
- data/lib/storytime/concerns/action_controller_extension.rb +36 -0
- data/lib/storytime/constraints/page_constraint.rb +8 -2
- data/lib/storytime/engine.rb +3 -5
- data/lib/storytime/migrators/v1.rb +3 -3
- data/lib/storytime/post_notifier.rb +1 -1
- data/lib/storytime/post_url_handler.rb +18 -5
- data/lib/storytime/storytime_helpers.rb +4 -0
- data/lib/storytime/version.rb +1 -1
- data/lib/storytime.rb +1 -1
- data/spec/controllers/dashboard_controller_spec.rb +5 -6
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/config/database.yml +3 -3
- data/spec/dummy/config/initializers/devise.rb +1 -1
- data/spec/dummy/db/migrate/20140530185250_devise_create_users.rb +1 -1
- data/spec/dummy/db/migrate/20150127172846_create_widgets.rb +1 -1
- data/spec/dummy/db/migrate/20150206203824_add_video_url_to_storytime_posts.rb +1 -1
- data/spec/dummy/db/schema.rb +226 -244
- data/spec/factories/action_factories.rb +3 -3
- data/spec/factories/comment_factories.rb +1 -1
- data/spec/factories/media_factories.rb +1 -1
- data/spec/factories/membership_factories.rb +1 -1
- data/spec/factories/navigation_factories.rb +3 -3
- data/spec/factories/permission_factories.rb +1 -1
- data/spec/factories/post_factories.rb +2 -2
- data/spec/factories/role_factories.rb +11 -11
- data/spec/factories/site_factories.rb +3 -3
- data/spec/factories/snippet_factories.rb +1 -1
- data/spec/factories/subscription_factories.rb +1 -1
- data/spec/factories/user_factories.rb +2 -2
- data/spec/factories/widget_factories.rb +2 -2
- data/spec/features/blogs_spec.rb +7 -7
- data/spec/features/comments_spec.rb +11 -11
- data/spec/features/dashboard/media_spec.rb +17 -11
- data/spec/features/dashboard/memberships_spec.rb +13 -19
- data/spec/features/dashboard/navigations_spec.rb +1 -1
- data/spec/features/dashboard/pages_spec.rb +8 -8
- data/spec/features/dashboard/posts_spec.rb +19 -19
- data/spec/features/dashboard/sites_spec.rb +1 -1
- data/spec/features/dashboard/snippets_spec.rb +3 -3
- data/spec/features/dashboard/subscription_spec.rb +2 -2
- data/spec/features/pages_spec.rb +3 -3
- data/spec/features/posts_spec.rb +2 -2
- data/spec/features/subscription_spec.rb +3 -3
- data/spec/importers/wordpress_spec.rb +1 -1
- data/spec/lib/mysql_fulltext_search_adapter_spec.rb +3 -3
- data/spec/lib/mysql_search_adapter_spec.rb +3 -3
- data/spec/lib/postgres_search_adapter_spec.rb +3 -3
- data/spec/lib/sqlite3_search_adapter_spec.rb +3 -3
- data/spec/lib/storytime/constraints/page_constraint_spec.rb +40 -0
- data/spec/lib/storytime_helpers_spec.rb +2 -2
- data/spec/models/navigation_spec.rb +3 -3
- data/spec/models/post_spec.rb +29 -29
- data/spec/models/snippet_spec.rb +31 -0
- data/spec/models/subscription_spec.rb +4 -4
- data/spec/models/tagging_spec.rb +14 -14
- data/spec/models/version_spec.rb +29 -29
- data/spec/policies/comment_policy_spec.rb +11 -11
- data/spec/policies/post_policy_spec.rb +13 -13
- data/spec/requests/pages_spec.rb +37 -0
- data/spec/requests/routings_spec.rb +14 -15
- data/spec/spec_helper.rb +8 -15
- data/spec/support/domains.rb +2 -2
- data/spec/support/feature_macros.rb +5 -5
- data/spec/support/pundit_matcher.rb +3 -3
- data/storytime.gemspec +15 -20
- data/vendor/assets/javascripts/codemirror/addons/edit/closebrackets.js +195 -0
- data/vendor/assets/javascripts/codemirror/addons/edit/closetag.js +169 -0
- data/vendor/assets/javascripts/codemirror/addons/fold/xml-fold.js +182 -0
- data/vendor/assets/javascripts/codemirror.js +8922 -0
- data/vendor/assets/javascripts/medium-editor.min.js +3 -3
- data/vendor/assets/stylesheets/codemirror/themes/solarized.css +169 -0
- data/vendor/assets/stylesheets/codemirror.css +347 -0
- metadata +94 -134
- data/config/initializers/storytime_admin.rb +0 -5
- data/config/spring.rb +0 -1
- data/spec/dummy/app/controllers/storytime_admin/widgets_controller.rb +0 -5
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Storytime::Constraints::PageConstraint do
|
|
4
|
+
let(:site) { FactoryBot.create(:site) }
|
|
5
|
+
let(:constraint) { described_class.new }
|
|
6
|
+
|
|
7
|
+
def request_for(id)
|
|
8
|
+
instance_double("ActionDispatch::Request", params: { id: id }, host: site.custom_domain)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
Storytime::Site.current_id = site.id
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after do
|
|
16
|
+
Storytime::Site.current_id = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "matches an existing page slug" do
|
|
20
|
+
page = FactoryBot.create(:page, site: site)
|
|
21
|
+
expect(constraint.matches?(request_for(page.slug))).to be true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "does not match an unknown slug" do
|
|
25
|
+
expect(constraint.matches?(request_for("no-such-page"))).to be false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
context "with a path-traversal payload" do
|
|
29
|
+
let(:payload) { "a/../../../../../../../../etc/passwd" }
|
|
30
|
+
|
|
31
|
+
it "does not match" do
|
|
32
|
+
expect(constraint.matches?(request_for(payload))).to be false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "never reaches the filesystem check" do
|
|
36
|
+
expect(File).not_to receive(:exist?)
|
|
37
|
+
constraint.matches?(request_for(payload))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -6,7 +6,7 @@ include Storytime::StorytimeHelpers
|
|
|
6
6
|
describe "Storytime::StorytimeHelpers", :type => :helper do
|
|
7
7
|
describe "storytime_snippet" do
|
|
8
8
|
before(:each) do
|
|
9
|
-
@site =
|
|
9
|
+
@site = FactoryBot.create(:site)
|
|
10
10
|
expect_any_instance_of(Storytime::StorytimeHelpers).to receive(:current_storytime_site).at_least(:once).and_return(@site)
|
|
11
11
|
expect_any_instance_of(ApplicationHelper).to receive(:logged_in_storytime_user?).at_least(:once).and_return(false)
|
|
12
12
|
end
|
|
@@ -27,7 +27,7 @@ describe "Storytime::StorytimeHelpers", :type => :helper do
|
|
|
27
27
|
|
|
28
28
|
describe "when snippet is found" do
|
|
29
29
|
it "returns snippet partial" do
|
|
30
|
-
snippet =
|
|
30
|
+
snippet = FactoryBot.create(:snippet, site: @site)
|
|
31
31
|
snippet_div = storytime_snippet(snippet.name)
|
|
32
32
|
|
|
33
33
|
expect(snippet_div).to include(snippet.content)
|
|
@@ -3,17 +3,17 @@ require 'spec_helper'
|
|
|
3
3
|
describe Storytime::Navigation do
|
|
4
4
|
describe "#handle" do
|
|
5
5
|
it "parameterizes the title to set the handle" do
|
|
6
|
-
nav =
|
|
6
|
+
nav = FactoryBot.create(:navigation, handle: nil)
|
|
7
7
|
expect(nav.handle).to eq nav.name.parameterize
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
it "parameterizes the handle" do
|
|
11
|
-
nav =
|
|
11
|
+
nav = FactoryBot.create(:navigation, handle: "my Handle")
|
|
12
12
|
expect(nav.handle).to eq "my-handle"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it "does not change the handle if the title changes" do
|
|
16
|
-
nav =
|
|
16
|
+
nav = FactoryBot.create(:navigation, handle: "my Handle")
|
|
17
17
|
nav.update(name: "New Name")
|
|
18
18
|
expect(nav.handle).to eq "my-handle"
|
|
19
19
|
end
|
data/spec/models/post_spec.rb
CHANGED
|
@@ -7,11 +7,11 @@ describe Storytime::Post do
|
|
|
7
7
|
after{ Storytime::BlogPost.instance_variable_set "@_to_partial_path", nil }
|
|
8
8
|
|
|
9
9
|
it "includes site in the path" do
|
|
10
|
-
allow(File).to receive(:
|
|
10
|
+
allow(File).to receive(:exist?).and_return(true)
|
|
11
|
+
|
|
12
|
+
site = FactoryBot.create(:site, title: "Test Site")
|
|
13
|
+
blog_post = FactoryBot.create(:post, site: site)
|
|
11
14
|
|
|
12
|
-
site = FactoryGirl.create(:site, title: "Test Site")
|
|
13
|
-
blog_post = FactoryGirl.create(:post, site: site)
|
|
14
|
-
|
|
15
15
|
partial_path = blog_post.to_partial_path
|
|
16
16
|
expect(partial_path).to eq("storytime/test-site/blog_posts/blog_post")
|
|
17
17
|
Storytime::BlogPost.instance_variable_set "@_to_partial_path", nil
|
|
@@ -20,7 +20,7 @@ describe Storytime::Post do
|
|
|
20
20
|
it "looks up the inheritance chain" do
|
|
21
21
|
allow(File).to receive(:exists?).and_return(false)
|
|
22
22
|
|
|
23
|
-
site =
|
|
23
|
+
site = FactoryBot.build(:site, title: "Test Site")
|
|
24
24
|
video_post = VideoPost.new(site: site)
|
|
25
25
|
partial_path = video_post.to_partial_path
|
|
26
26
|
expect(partial_path).to eq("storytime/posts/post")
|
|
@@ -28,60 +28,60 @@ describe Storytime::Post do
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
it "sets the page slug on create" do
|
|
31
|
-
post =
|
|
32
|
-
post.slug.
|
|
31
|
+
post = FactoryBot.create(:post)
|
|
32
|
+
expect(post.slug).to eq(post.title.parameterize)
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
it "sets slug to user inputted value" do
|
|
36
|
-
post =
|
|
36
|
+
post = FactoryBot.create(:post)
|
|
37
37
|
|
|
38
38
|
post.slug = "random slug here"
|
|
39
39
|
post.save
|
|
40
40
|
|
|
41
|
-
post.slug.
|
|
41
|
+
expect(post.slug).to eq("random-slug-here")
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
it "does not allow the same slug" do
|
|
45
|
-
post_1 =
|
|
46
|
-
post_2 =
|
|
45
|
+
post_1 = FactoryBot.create(:post)
|
|
46
|
+
post_2 = FactoryBot.create(:post)
|
|
47
47
|
|
|
48
48
|
post_2.slug = post_1.slug
|
|
49
49
|
post_2.save
|
|
50
50
|
|
|
51
|
-
post_2.slug.
|
|
52
|
-
post_2.slug.
|
|
51
|
+
expect(post_2.slug).to_not eq(post_1.slug)
|
|
52
|
+
expect(post_2.slug).to include(post_1.slug)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
it "does not allow a blank slug" do
|
|
56
|
-
post =
|
|
56
|
+
post = FactoryBot.create(:post)
|
|
57
57
|
post.slug = ""
|
|
58
58
|
post.save
|
|
59
59
|
|
|
60
|
-
post.slug.
|
|
61
|
-
post.slug.
|
|
60
|
+
expect(post.slug).to_not eq("")
|
|
61
|
+
expect(post.slug).to eq(post.title.parameterize)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
it "creates tags from tag_list attribute" do
|
|
65
|
-
post =
|
|
65
|
+
post = FactoryBot.create(:post)
|
|
66
66
|
post.tag_list = ["tag1", "tag2"]
|
|
67
|
-
post.tags.count.
|
|
67
|
+
expect(post.tags.count).to eq(2)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
it "scopes posts by tag" do
|
|
71
|
-
post_1 =
|
|
72
|
-
post_2 =
|
|
71
|
+
post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
|
|
72
|
+
post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
|
|
73
73
|
|
|
74
|
-
Storytime::Post.tagged_with("tag1").
|
|
75
|
-
Storytime::Post.tagged_with("tag1").
|
|
76
|
-
Storytime::Post.tagged_with("tag2").
|
|
77
|
-
Storytime::Post.tagged_with("tag2").
|
|
74
|
+
expect(Storytime::Post.tagged_with("tag1")).to include(post_1)
|
|
75
|
+
expect(Storytime::Post.tagged_with("tag1")).to include(post_2)
|
|
76
|
+
expect(Storytime::Post.tagged_with("tag2")).to include(post_1)
|
|
77
|
+
expect(Storytime::Post.tagged_with("tag2")).to_not include(post_2)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
it "counts tags across posts" do
|
|
81
|
-
post_1 =
|
|
82
|
-
post_2 =
|
|
81
|
+
post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
|
|
82
|
+
post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
|
|
83
83
|
|
|
84
|
-
Storytime::Post.tag_counts.find_by(name: "tag1").count.
|
|
85
|
-
Storytime::Post.tag_counts.find_by(name: "tag2").count.
|
|
84
|
+
expect(Storytime::Post.tag_counts.find_by(name: "tag1").count).to eq(2)
|
|
85
|
+
expect(Storytime::Post.tag_counts.find_by(name: "tag2").count).to eq(1)
|
|
86
86
|
end
|
|
87
|
-
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Storytime::Snippet do
|
|
4
|
+
describe "#sanitize_content" do
|
|
5
|
+
it "strips script-bearing attributes from content on save" do
|
|
6
|
+
snippet = FactoryBot.create(:snippet, content: %(<img src=x onerror="alert(1)">))
|
|
7
|
+
expect(snippet.content).to_not include("onerror")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "strips <script> tags from content on save" do
|
|
11
|
+
snippet = FactoryBot.create(:snippet, content: %(hello<script>alert(1)</script>))
|
|
12
|
+
expect(snippet.content).to_not include("<script")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "re-sanitizes content on update" do
|
|
16
|
+
snippet = FactoryBot.create(:snippet, content: "safe")
|
|
17
|
+
snippet.update(content: %(<a href="javascript:alert(1)">x</a>))
|
|
18
|
+
expect(snippet.content).to_not include("javascript:")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "preserves allowed markup" do
|
|
22
|
+
snippet = FactoryBot.create(:snippet, content: %(<p>hello <strong>world</strong></p>))
|
|
23
|
+
expect(snippet.content).to include("<strong>world</strong>")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "leaves plain text unchanged" do
|
|
27
|
+
snippet = FactoryBot.create(:snippet, content: "just plain text")
|
|
28
|
+
expect(snippet.content).to eq("just plain text")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
|
3
3
|
describe Storytime::Subscription do
|
|
4
4
|
describe "generate_token" do
|
|
5
5
|
it "generates a token before creation" do
|
|
6
|
-
subscription =
|
|
6
|
+
subscription = FactoryBot.build(:subscription)
|
|
7
7
|
|
|
8
8
|
expect(subscription.token).to eq(nil)
|
|
9
9
|
|
|
@@ -13,10 +13,10 @@ describe Storytime::Subscription do
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
it "generates a token that can be regenerated from the email" do
|
|
16
|
-
subscription =
|
|
16
|
+
subscription = FactoryBot.create(:subscription)
|
|
17
17
|
token = subscription.token
|
|
18
18
|
|
|
19
|
-
key = Rails.application.
|
|
19
|
+
key = Rails.application.secret_key_base
|
|
20
20
|
digest = OpenSSL::Digest.new('sha1')
|
|
21
21
|
regenerated_token = OpenSSL::HMAC.hexdigest(digest, key, subscription.email)
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ describe Storytime::Subscription do
|
|
|
26
26
|
|
|
27
27
|
describe "unsubscribe!" do
|
|
28
28
|
it "sets subscribed to false" do
|
|
29
|
-
subscription =
|
|
29
|
+
subscription = FactoryBot.create(:subscription)
|
|
30
30
|
|
|
31
31
|
expect(subscription.subscribed?).to eq(true)
|
|
32
32
|
|
data/spec/models/tagging_spec.rb
CHANGED
|
@@ -2,28 +2,28 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe Storytime::Tagging do
|
|
4
4
|
it "unused tags remain after deleting a post" do
|
|
5
|
-
post_1 =
|
|
6
|
-
post_2 =
|
|
7
|
-
Storytime::Tag.all.count.
|
|
5
|
+
post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
|
|
6
|
+
post_2 = FactoryBot.create(:post, tag_list: ["tag1"])
|
|
7
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
8
8
|
post_1.destroy
|
|
9
|
-
Storytime::Tag.all.count.
|
|
9
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
10
10
|
post_2.destroy
|
|
11
|
-
Storytime::Tag.all.count.
|
|
11
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it "does not remove unused tags after updating tag list" do
|
|
15
|
-
post_1 =
|
|
16
|
-
Storytime::Tag.all.count.
|
|
15
|
+
post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
|
|
16
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
17
17
|
post_1.update(tag_list: ["tag1"])
|
|
18
|
-
Storytime::Tag.all.count.
|
|
19
|
-
post_1.tags.count.
|
|
18
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
19
|
+
expect(post_1.tags.count).to eq(1)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
it "does not removes tags used by other posts after updating tag list" do
|
|
23
|
-
post_1 =
|
|
24
|
-
post_2 =
|
|
25
|
-
Storytime::Tag.all.count.
|
|
23
|
+
post_1 = FactoryBot.create(:post, tag_list: ["tag1", "tag2"])
|
|
24
|
+
post_2 = FactoryBot.create(:post, tag_list: ["tag2"])
|
|
25
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
26
26
|
post_1.update(tag_list: ["tag1"])
|
|
27
|
-
Storytime::Tag.all.count.
|
|
27
|
+
expect(Storytime::Tag.all.count).to eq(2)
|
|
28
28
|
end
|
|
29
|
-
end
|
|
29
|
+
end
|
data/spec/models/version_spec.rb
CHANGED
|
@@ -3,55 +3,55 @@ require 'spec_helper'
|
|
|
3
3
|
describe Storytime::Version do
|
|
4
4
|
|
|
5
5
|
it "creates a version when creating a Post" do
|
|
6
|
-
Storytime::Version.all.count.
|
|
7
|
-
user =
|
|
8
|
-
post =
|
|
9
|
-
post.
|
|
10
|
-
Storytime::Version.all.count.
|
|
11
|
-
post.latest_version.
|
|
12
|
-
post.latest_version.content.
|
|
13
|
-
post.latest_version.user.
|
|
6
|
+
expect(Storytime::Version.all.count).to eq(0)
|
|
7
|
+
user = FactoryBot.create(:user)
|
|
8
|
+
post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
|
|
9
|
+
expect(post).to_not be_published
|
|
10
|
+
expect(Storytime::Version.all.count).to eq(1)
|
|
11
|
+
expect(post.latest_version).to eq(Storytime::Version.last)
|
|
12
|
+
expect(post.latest_version.content).to eq("Testing 123")
|
|
13
|
+
expect(post.latest_version.user).to eq(user)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
it "publishes a post with latest_version content" do
|
|
17
|
-
user =
|
|
18
|
-
post =
|
|
19
|
-
post.
|
|
20
|
-
post.content.
|
|
17
|
+
user = FactoryBot.create(:user)
|
|
18
|
+
post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
|
|
19
|
+
expect(post).to_not be_published
|
|
20
|
+
expect(post.content).to_not eq("Testing 123")
|
|
21
21
|
post.publish!
|
|
22
|
-
post.
|
|
23
|
-
post.content.
|
|
22
|
+
expect(post).to be_published
|
|
23
|
+
expect(post.content).to eq("Testing 123")
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it "returns the latest version content as draft_content" do
|
|
27
|
-
user =
|
|
28
|
-
post =
|
|
27
|
+
user = FactoryBot.create(:user)
|
|
28
|
+
post = FactoryBot.create(:post, published_at: nil, user: user, draft_user_id: user.id, draft_content: "Testing 123")
|
|
29
29
|
post.reload
|
|
30
|
-
post.draft_content.
|
|
30
|
+
expect(post.draft_content).to eq(post.latest_version.content)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
it "does not create a version when content does not change" do
|
|
34
|
-
post =
|
|
35
|
-
Storytime::Version.all.count.
|
|
34
|
+
post = FactoryBot.create(:post, published_at: nil, draft_content: "Testing 123")
|
|
35
|
+
expect(Storytime::Version.all.count).to eq(1)
|
|
36
36
|
post.update(excerpt: "New Excerpt")
|
|
37
|
-
Storytime::Version.all.count.
|
|
37
|
+
expect(Storytime::Version.all.count).to eq(1)
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
it "creates a version when content is updated" do
|
|
41
|
-
post =
|
|
42
|
-
Storytime::Version.all.count.
|
|
41
|
+
post = FactoryBot.create(:post, draft_content: "Testing 123")
|
|
42
|
+
expect(Storytime::Version.all.count).to eq(1)
|
|
43
43
|
post.update(draft_content: "New Content")
|
|
44
|
-
Storytime::Version.all.count.
|
|
45
|
-
post.content.
|
|
44
|
+
expect(Storytime::Version.all.count).to eq(2)
|
|
45
|
+
expect(post.content).to eq("New Content")
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
it "reverts to a previous version" do
|
|
49
|
-
user =
|
|
50
|
-
post =
|
|
49
|
+
user = FactoryBot.create(:user)
|
|
50
|
+
post = FactoryBot.create(:post, user: user)
|
|
51
51
|
version1 = Storytime::Version.last
|
|
52
52
|
post.update(draft_content: "New Content", draft_user_id: user.id)
|
|
53
|
-
post.content.
|
|
53
|
+
expect(post.content).to eq("New Content")
|
|
54
54
|
post.update(draft_version_id: version1.id)
|
|
55
|
-
post.content.
|
|
55
|
+
expect(post.content).to eq(version1.content)
|
|
56
56
|
end
|
|
57
|
-
end
|
|
57
|
+
end
|
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe Storytime::CommentPolicy do
|
|
4
4
|
subject { Storytime::CommentPolicy.new(user, comment) }
|
|
5
|
-
let(:site) {
|
|
6
|
-
let(:post) {
|
|
5
|
+
let(:site) { FactoryBot.create(:site) }
|
|
6
|
+
let(:post) { FactoryBot.create(:post) }
|
|
7
7
|
|
|
8
8
|
context "as a normal user" do
|
|
9
9
|
before do
|
|
@@ -11,10 +11,10 @@ describe Storytime::CommentPolicy do
|
|
|
11
11
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(false)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
let(:user) {
|
|
14
|
+
let(:user) { FactoryBot.create(:user) }
|
|
15
15
|
|
|
16
16
|
context "a comment owned by the user" do
|
|
17
|
-
let(:comment) {
|
|
17
|
+
let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
|
|
18
18
|
|
|
19
19
|
it { should permit!(:create) }
|
|
20
20
|
it { should permit!(:destroy) }
|
|
@@ -22,7 +22,7 @@ describe Storytime::CommentPolicy do
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
context "a comment not owned by the user" do
|
|
25
|
-
let(:comment) {
|
|
25
|
+
let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
|
|
26
26
|
|
|
27
27
|
it { should_not permit!(:create) }
|
|
28
28
|
it { should_not permit!(:destroy) }
|
|
@@ -35,17 +35,17 @@ describe Storytime::CommentPolicy do
|
|
|
35
35
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(true)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
let(:user){
|
|
38
|
+
let(:user){ FactoryBot.create(:editor) }
|
|
39
39
|
|
|
40
40
|
context "a comment owned by the user" do
|
|
41
|
-
let(:comment) {
|
|
41
|
+
let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
|
|
42
42
|
|
|
43
43
|
it { should permit!(:create) }
|
|
44
44
|
it { should permit!(:destroy) }
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
context "a comment not owned by the user" do
|
|
48
|
-
let(:comment) {
|
|
48
|
+
let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
|
|
49
49
|
|
|
50
50
|
it { should_not permit!(:create) }
|
|
51
51
|
it { should permit!(:destroy) }
|
|
@@ -57,17 +57,17 @@ describe Storytime::CommentPolicy do
|
|
|
57
57
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_admin?).and_return(true)
|
|
58
58
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_editor?).and_return(false)
|
|
59
59
|
end
|
|
60
|
-
let(:user){
|
|
60
|
+
let(:user){ FactoryBot.create(:admin) }
|
|
61
61
|
|
|
62
62
|
context "a comment owned by the user" do
|
|
63
|
-
let(:comment) {
|
|
63
|
+
let(:comment) { FactoryBot.build(:comment, user: user, post: post, site: site) }
|
|
64
64
|
|
|
65
65
|
it { should permit!(:create) }
|
|
66
66
|
it { should permit!(:destroy) }
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
context "a comment not owned by the user" do
|
|
70
|
-
let(:comment) {
|
|
70
|
+
let(:comment) { FactoryBot.build(:comment, post: post, site: site) }
|
|
71
71
|
|
|
72
72
|
it { should_not permit!(:create) }
|
|
73
73
|
it { should permit!(:destroy) }
|
|
@@ -5,7 +5,7 @@ require 'spec_helper'
|
|
|
5
5
|
describe Storytime::PostPolicy do
|
|
6
6
|
subject { Storytime::PostPolicy.new(user, post) }
|
|
7
7
|
|
|
8
|
-
let(:site) {
|
|
8
|
+
let(:site) { FactoryBot.create(:site) }
|
|
9
9
|
before do
|
|
10
10
|
site.save_with_seeds(user)
|
|
11
11
|
allow(Storytime::Site).to receive(:current).and_return(site)
|
|
@@ -17,17 +17,17 @@ describe Storytime::PostPolicy do
|
|
|
17
17
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_role_in_site).and_return(Storytime::Role.find_by(name: "writer"))
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
let(:user) {
|
|
20
|
+
let(:user) { FactoryBot.create(:writer) }
|
|
21
21
|
|
|
22
22
|
context "creating a new post" do
|
|
23
|
-
let(:post) {
|
|
23
|
+
let(:post) { FactoryBot.build(:post, user: user) }
|
|
24
24
|
|
|
25
25
|
it { should permit!(:new) }
|
|
26
26
|
it { should permit!(:create) }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
context "who owns the post" do
|
|
30
|
-
let(:post) {
|
|
30
|
+
let(:post) { FactoryBot.build_stubbed :post, user: user }
|
|
31
31
|
|
|
32
32
|
it { should permit!(:index) }
|
|
33
33
|
it { should permit!(:manage) } # edit, update, destroy
|
|
@@ -35,7 +35,7 @@ describe Storytime::PostPolicy do
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
context "who does not own the post" do
|
|
38
|
-
let(:post) {
|
|
38
|
+
let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
|
|
39
39
|
|
|
40
40
|
it { should permit!(:index) }
|
|
41
41
|
it { should_not permit!(:manage) } # edit, update, destroy
|
|
@@ -48,17 +48,17 @@ describe Storytime::PostPolicy do
|
|
|
48
48
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_role).and_return(Storytime::Role.find_by(name: "editor"))
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
let(:user) {
|
|
51
|
+
let(:user) { FactoryBot.create(:editor) }
|
|
52
52
|
|
|
53
53
|
context "creating a new post" do
|
|
54
|
-
let(:post) {
|
|
54
|
+
let(:post) { FactoryBot.build(:post, user: user) }
|
|
55
55
|
|
|
56
56
|
it { should permit!(:new) }
|
|
57
57
|
it { should permit!(:create) }
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
context "who owns the post" do
|
|
61
|
-
let(:post) {
|
|
61
|
+
let(:post) { FactoryBot.build_stubbed :post, user: user }
|
|
62
62
|
|
|
63
63
|
it { should permit!(:index) }
|
|
64
64
|
it { should permit!(:manage) } # edit, update, destroy
|
|
@@ -66,7 +66,7 @@ describe Storytime::PostPolicy do
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
context "who does not own the post" do
|
|
69
|
-
let(:post) {
|
|
69
|
+
let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
|
|
70
70
|
|
|
71
71
|
it { should permit!(:index) }
|
|
72
72
|
it { should permit!(:manage) } # edit, update, destroy
|
|
@@ -79,17 +79,17 @@ describe Storytime::PostPolicy do
|
|
|
79
79
|
allow_any_instance_of(Storytime.user_class).to receive(:storytime_role).and_return(Storytime::Role.find_by(name: "admin"))
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
let(:user) {
|
|
82
|
+
let(:user) { FactoryBot.create(:admin) }
|
|
83
83
|
|
|
84
84
|
context "creating a new post" do
|
|
85
|
-
let(:post) {
|
|
85
|
+
let(:post) { FactoryBot.build(:post, user: user) }
|
|
86
86
|
|
|
87
87
|
it { should permit!(:new) }
|
|
88
88
|
it { should permit!(:create) }
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
context "who owns the post" do
|
|
92
|
-
let(:post) {
|
|
92
|
+
let(:post) { FactoryBot.build_stubbed :post, user: user }
|
|
93
93
|
|
|
94
94
|
it { should permit!(:index) }
|
|
95
95
|
it { should permit!(:manage) } # edit, update, destroy
|
|
@@ -97,7 +97,7 @@ describe Storytime::PostPolicy do
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
context "who does not own the post" do
|
|
100
|
-
let(:post) {
|
|
100
|
+
let(:post) { FactoryBot.build_stubbed :post, user: FactoryBot.build(:user) }
|
|
101
101
|
|
|
102
102
|
it { should permit!(:index) }
|
|
103
103
|
it { should permit!(:manage) } # edit, update, destroy
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# Request spec rather than controller spec so we go through the real engine
|
|
4
|
+
# routes – the bug only manifests after the route's PageConstraint admits the
|
|
5
|
+
# request to the controller.
|
|
6
|
+
describe "Pages", type: :request do
|
|
7
|
+
let(:user) { FactoryBot.create(:admin) }
|
|
8
|
+
let!(:site) do
|
|
9
|
+
s = FactoryBot.create(:site, custom_domain: "www.example.com")
|
|
10
|
+
s.save_with_seeds(user)
|
|
11
|
+
s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
# Force the route's PageConstraint to admit the request so we can exercise
|
|
16
|
+
# the controller for ids the constraint would normally reject (like the
|
|
17
|
+
# leading-slash slugs that bots probe).
|
|
18
|
+
allow_any_instance_of(Storytime::Constraints::PageConstraint)
|
|
19
|
+
.to receive(:matches?).and_return(true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "GET /:id when no page or slug-specific template matches" do
|
|
23
|
+
it "404s instead of falling through to a show template that requires @page" do
|
|
24
|
+
get "http://www.example.com/no-such-page"
|
|
25
|
+
expect(response).to have_http_status(:not_found)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "404s for malformed ids with a leading slash" do
|
|
29
|
+
# Bots hit URL-encoded paths like /%2Fcontact. The PageConstraint can let
|
|
30
|
+
# these through because POSIX collapses // in File.exist?, but the
|
|
31
|
+
# template resolver does not, so the controller must 404 explicitly
|
|
32
|
+
# rather than rendering the @page-dependent show template.
|
|
33
|
+
get "http://www.example.com/%2Fno-such-page"
|
|
34
|
+
expect(response).to have_http_status(:not_found)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|