diff --git a/Gemfile b/Gemfile index e8caccdfc..4ad11ea2c 100644 --- a/Gemfile +++ b/Gemfile @@ -63,6 +63,7 @@ group :development, :test do gem 'sdoc', require: false gem 'capybara', '~> 2.17' + gem 'nokogiri' gem 'selenium-webdriver', require: false gem 'pry' diff --git a/Gemfile.lock b/Gemfile.lock index cb982ef4d..24ea95c4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -416,6 +416,7 @@ DEPENDENCIES listen (>= 3.0.5, < 3.2) minitest-hooks mysql2 (>= 0.3.18, < 0.5) + nokogiri omniauth omniauth-saml pry @@ -446,4 +447,4 @@ DEPENDENCIES wicked BUNDLED WITH - 1.16.0 + 1.16.1 diff --git a/app/controllers/robots_controller.rb b/app/controllers/robots_controller.rb new file mode 100644 index 000000000..16ca82669 --- /dev/null +++ b/app/controllers/robots_controller.rb @@ -0,0 +1,10 @@ +class RobotsController < ApplicationController + + skip_after_action :verify_authorized + + def robots + respond_to :text + expires_in 6.hours, public: true + end + +end diff --git a/app/controllers/sitemap_controller.rb b/app/controllers/sitemap_controller.rb new file mode 100644 index 000000000..7fff6600b --- /dev/null +++ b/app/controllers/sitemap_controller.rb @@ -0,0 +1,28 @@ +class SitemapController < ApplicationController + + skip_after_action :verify_authorized + + def index; end + + def communities + @communities = Community.all + # TODO: consider using Rollbar to catch this kind of thing + logger.warn 'communities sitemap should contain less than 50,000 targets' if @communities.count > 50_000 + end + + def collections + @collections = Collection.all + logger.warn 'collections sitemap should contain less than 50,000 targets' if @collections.count > 50_000 + end + + def items + @items = Item.public + logger.warn 'items sitemap should contain less than 50,000 targets' if @items.count > 50_000 + end + + def theses + @theses = Thesis.public + logger.warn 'thesis sitemap should contain less than 50,000 targets' if @theses.count > 50_000 + end + +end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index fa77bf21e..6d44dbb0c 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -16,4 +16,9 @@ def license_link(license) text ||= license link_to(text, license) end + + def authors(object) + return [object.dissertant] if object.respond_to? :dissertant + return object.creators if object.respond_to? :creators + end end diff --git a/app/models/concerns/item_properties.rb b/app/models/concerns/item_properties.rb index 7f8e50ddb..ab59a0b8f 100644 --- a/app/models/concerns/item_properties.rb +++ b/app/models/concerns/item_properties.rb @@ -116,6 +116,20 @@ def add_files(files) else File.basename(file) end + # Store file properties in the format required by the sitemap + # for quick and easy retrieval -- nobody wants to wait 36hrs for this! + unlocked_fileset.sitemap_link = "" unlocked_fileset.save! self.members += [unlocked_fileset] # pull in hydra derivatives, set temp file base @@ -134,6 +148,10 @@ def display_attribute_names def valid_visibilities super + [VISIBILITY_EMBARGO] end + + def public + where(visibility: JupiterCore::VISIBILITY_PUBLIC) + end end def doi_url diff --git a/app/models/file_set.rb b/app/models/file_set.rb index 64d339b92..237cca237 100644 --- a/app/models/file_set.rb +++ b/app/models/file_set.rb @@ -6,6 +6,7 @@ class FileSet < JupiterCore::LockedLdpObject ldp_object_includes Hydra::Works::FileSetBehavior has_attribute :contained_filename, ::RDF::Vocab::DC.title, solrize_for: :exact_match + has_attribute :sitemap_link, ::TERMS[:ual].sitemap_link, solrize_for: :exact_match belongs_to :item, using_existing_association: :member_of_collections diff --git a/app/views/items/_google_scholar_metadata.html.erb b/app/views/items/_google_scholar_metadata.html.erb new file mode 100644 index 000000000..4b5f7425f --- /dev/null +++ b/app/views/items/_google_scholar_metadata.html.erb @@ -0,0 +1,13 @@ + + +<% authors(@item).each do |author| %> + +<% end %> +<% if @item.sort_year.present? %> + +<% end %> +<% if @item.file_sets.count > 0 %> + +<% end %> + + diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb index 93a42286d..ed7a45154 100644 --- a/app/views/items/show.html.erb +++ b/app/views/items/show.html.erb @@ -1,3 +1,7 @@ +<% content_for :head do %> + <%= render partial: 'google_scholar_metadata' %> +<% end %> + <% page_title(@item.title) %> <%# Breadcrumbs here are for 'Home->Search->Item' %> diff --git a/app/views/robots/robots.text.erb b/app/views/robots/robots.text.erb new file mode 100644 index 000000000..2b3668e5c --- /dev/null +++ b/app/views/robots/robots.text.erb @@ -0,0 +1,7 @@ +# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file +Sitemap: https://era.library.ualberta.ca/sitemap.xml + +<% unless Rails.configuration.allow_crawlers %> +User-agent: * +Disallow: / +<% end %> diff --git a/app/views/sitemap/_object.xml.builder b/app/views/sitemap/_object.xml.builder new file mode 100644 index 000000000..7ca3dc65c --- /dev/null +++ b/app/views/sitemap/_object.xml.builder @@ -0,0 +1,27 @@ +xml.instruct! :xml, version: '1.0' +cache 'sitemap', expires_in: 24.hours do + xml.urlset(xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9', + 'xmlns:rs' => 'http://www.openarchives.org/rs/terms/') do + xml.rs :md, capability: 'resourcelist', at: Time.current.iso8601 + xml.url do + xml.loc root_url + xml.lastmod Time.current.iso8601 + xml.changefreq 'weekly' + xml.priority 1 + xml.rs :md, type: 'text/html' + end + + objects.each do |object| + xml.url do + xml.loc item_url(object) + xml.lastmod object.updated_at + xml.changefreq 'weekly' + xml.priority 1 + xml.rs :md, type: 'text/html' + object.file_sets.each do |file_set| + xml << "\t#{file_set.sitemap_link}\n" + end + end + end + end +end diff --git a/app/views/sitemap/collections.xml.builder b/app/views/sitemap/collections.xml.builder new file mode 100644 index 000000000..a98403e97 --- /dev/null +++ b/app/views/sitemap/collections.xml.builder @@ -0,0 +1,20 @@ +xml.instruct! :xml, version: '1.0' +cache 'sitemap', expires_in: 24.hours do + xml.urlset(xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9') do + xml.url do + xml.loc root_url + xml.lastmod Time.current.utc.iso8601 + xml.changefreq 'weekly' + xml.priority 1 + end + + @collections.each do |collection| + xml.url do + xml.loc community_collection_url(collection.community, collection) + xml.lastmod collection.updated_at + xml.changefreq 'weekly' + xml.priority 1 + end + end + end +end diff --git a/app/views/sitemap/communities.xml.builder b/app/views/sitemap/communities.xml.builder new file mode 100644 index 000000000..d963535b6 --- /dev/null +++ b/app/views/sitemap/communities.xml.builder @@ -0,0 +1,20 @@ +xml.instruct! :xml, version: '1.0' +cache 'sitemap', expires_in: 24.hours do + xml.urlset(xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9') do + xml.url do + xml.loc root_url + xml.lastmod Time.current.utc.iso8601 + xml.changefreq 'weekly' + xml.priority 1 + end + + @communities.each do |community| + xml.url do + xml.loc community_url(community) + xml.lastmod community.updated_at + xml.changefreq 'weekly' + xml.priority 1 + end + end + end +end diff --git a/app/views/sitemap/index.xml.builder b/app/views/sitemap/index.xml.builder new file mode 100644 index 000000000..24c7d738a --- /dev/null +++ b/app/views/sitemap/index.xml.builder @@ -0,0 +1,8 @@ +xml.instruct! :xml, version: '1.0' +xml.sitemapindex xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' do + [:items, :theses, :collections, :communities].each do |category| + xml.sitemap do + xml.loc url_for(action: category, controller: 'sitemap', only_path: false) + end + end +end diff --git a/app/views/sitemap/items.xml.builder b/app/views/sitemap/items.xml.builder new file mode 100644 index 000000000..0aa9283d3 --- /dev/null +++ b/app/views/sitemap/items.xml.builder @@ -0,0 +1 @@ +xml << render(partial: 'object', locals: { objects: @items }) diff --git a/app/views/sitemap/theses.xml.builder b/app/views/sitemap/theses.xml.builder new file mode 100644 index 000000000..317c0eb58 --- /dev/null +++ b/app/views/sitemap/theses.xml.builder @@ -0,0 +1 @@ +xml << render(partial: 'object', locals: { objects: @theses }) diff --git a/config/application.rb b/config/application.rb index 1deeb5a85..500cca725 100644 --- a/config/application.rb +++ b/config/application.rb @@ -33,5 +33,8 @@ class Application < Rails::Application # Run skylight in UAT for performance metric monitoring pre-launch config.skylight.environments += ['uat'] + # we can tell crawlers to go away during the beta launch phase + config.allow_crawlers = ENV['RAILS_ALLOW_CRAWLERS'].present? + end end diff --git a/config/routes.rb b/config/routes.rb index b47346946..a51175d19 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -62,5 +62,14 @@ mount Sidekiq::Web => '/sidekiq', constraints: AdminConstraint.new end + get 'sitemap.xml', to: 'sitemap#index', defaults: { format: :xml }, as: :sitemapindex + get 'sitemap-communities.xml', to: 'sitemap#communities', defaults: { format: :xml }, as: :communities_sitemap + get 'sitemap-collections.xml', to: 'sitemap#collections', defaults: { format: :xml }, as: :collections_sitemap + get 'sitemap-items.xml', to: 'sitemap#items', defaults: { format: :xml }, as: :items_sitemap + get 'sitemap-theses.xml', to: 'sitemap#theses', defaults: { format: :xml } + + # Dynamic robots.txt + get 'robots.txt' => 'robots#robots' + root to: 'welcome#index' end diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index 37b576a4a..000000000 --- a/public/robots.txt +++ /dev/null @@ -1 +0,0 @@ -# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file diff --git a/test/controllers/sitemap_controller_test.rb b/test/controllers/sitemap_controller_test.rb new file mode 100644 index 000000000..34180dbe4 --- /dev/null +++ b/test/controllers/sitemap_controller_test.rb @@ -0,0 +1,162 @@ +require 'test_helper' +require 'open-uri' + +class SitemapTest < ActionDispatch::IntegrationTest + + def before_all + super + @community = Community.new_locked_ldp_object(title: 'Fancy Community', owner: 1) + .unlock_and_fetch_ldp_object(&:save!) + @collection = Collection.new_locked_ldp_object(community_id: @community.id, + title: 'Fancy Collection', owner: 1) + .unlock_and_fetch_ldp_object(&:save!) + @item = Item.new_locked_ldp_object(visibility: JupiterCore::VISIBILITY_PUBLIC, + owner: 1, title: 'Fancy Item', + creators: ['Joe Blow'], + created: '1938-01-02', + languages: [CONTROLLED_VOCABULARIES[:language].english], + item_type: CONTROLLED_VOCABULARIES[:item_type].article, + publication_status: [CONTROLLED_VOCABULARIES[:publication_status].published], + license: CONTROLLED_VOCABULARIES[:license].attribution_4_0_international, + subject: ['Items']) + .unlock_and_fetch_ldp_object do |uo| + uo.add_to_path(@community.id, @collection.id) + uo.save! + # Attach a file to the item so that it has attributes to check for + file = File.open(Rails.root + 'app/assets/images/mc_360.png', 'r') + uo.add_files([file]) + file.close + end + @thesis = Thesis.new_locked_ldp_object(visibility: JupiterCore::VISIBILITY_PUBLIC, + owner: 1, title: 'Fancy Item', + dissertant: 'Joe Blow', + language: CONTROLLED_VOCABULARIES[:language].english, + graduation_date: 'Fall 2017') + .unlock_and_fetch_ldp_object do |uo| + uo.add_to_path(@community.id, @collection.id) + uo.save! + end + # 1 more item. this is private + @private_item = Item.new_locked_ldp_object(visibility: JupiterCore::VISIBILITY_PRIVATE, + owner: 1, title: 'Fancy Private Item', + creators: ['Joe Blow'], + created: '1983-11-11', + languages: [CONTROLLED_VOCABULARIES[:language].english], + item_type: CONTROLLED_VOCABULARIES[:item_type].article, + publication_status: + [CONTROLLED_VOCABULARIES[:publication_status].published], + license: CONTROLLED_VOCABULARIES[:license].attribution_4_0_international, + subject: ['Items']) + .unlock_and_fetch_ldp_object do |uo| + uo.add_to_path(@community.id, @collection.id) + uo.save! + end + end + + context 'sitemap index' do + setup do + get sitemapindex_url + end + should 'be valid sitemapindex xml' do + schema = Nokogiri::XML::Schema(File.open(file_fixture('siteindex.xsd'))) + document = Nokogiri::XML(@response.body) + assert_empty schema.validate(document) + end + should 'show sitemap for each of communities, collections, items and thesis' do + assert_select 'sitemap', 4 + assert_select 'loc', /sitemap-items.xml/ + assert_select 'loc', /sitemap-theses.xml/ + assert_select 'loc', /sitemap-communities.xml/ + assert_select 'loc', /sitemap-collections.xml/ + end + end + + context 'items sitemap' do + setup do + get items_sitemap_url + end + should 'be valid sitemap xml' do + schema = Nokogiri::XML::Schema(File.open(file_fixture('sitemap.xsd'))) + document = Nokogiri::XML(@response.body) + assert_empty schema.validate(document) + end + should 'show url, last modified date, change frequency, priority and type' do + assert_select 'url' do + assert_select 'loc' + assert_select 'lastmod' + assert_select 'changefreq' + assert_select 'priority' + assert_select 'rs|md[type=?]', 'text/html' + end + end + should 'show public item attributes' do + assert_select 'loc', item_url(@item) + assert_select 'lastmod', @item.updated_at.to_s + @item.file_sets.first.unlock_and_fetch_ldp_object do |uo| + assert_select 'rs|ln[href=?]', url_for(controller: :file_sets, + action: :download, + id: uo.id, + file_name: uo.contained_filename, + only_path: true) + assert_select 'rs|ln[hash=?]', + "#{uo.original_file.checksum.algorithm.downcase}:#{uo.original_file.checksum.value}" + assert_select 'rs|ln[length=?]', uo.original_file.size.to_s + assert_select 'rs|ln[type=?]', uo.original_file.mime_type.to_s + end + end + + should 'not show private items' do + assert_select 'url', count: 2 + assert_select 'loc', { count: 0, text: item_url(@private_item) }, 'private items shant appear in the sitemap' + end + end + + context 'collections sitemap' do + setup do + get collections_sitemap_url + end + should 'be valid sitemap xml' do + get collections_sitemap_url + schema = Nokogiri::XML::Schema(File.open(file_fixture('sitemap.xsd'))) + document = Nokogiri::XML(@response.body) + assert_empty schema.validate(document) + end + should 'show url, last modified date, change frequency and priority' do + assert_select 'url' do + assert_select 'loc' + assert_select 'lastmod' + assert_select 'changefreq' + assert_select 'priority' + end + end + should 'show location and last modified' do + assert_select 'loc', community_collection_url(@collection.community, @collection) + assert_select 'lastmod', @collection.updated_at.to_s + end + end + + context 'communities sitemap' do + setup do + get communities_sitemap_url + end + should 'be valid sitemap xml' do + get communities_sitemap_url + schema = Nokogiri::XML::Schema(File.open(file_fixture('sitemap.xsd'))) + document = Nokogiri::XML(@response.body) + assert_empty schema.validate(document) + end + should 'show url, last modified date, change frequency and priority' do + assert_select 'url' do + assert_select 'loc' + assert_select 'lastmod' + assert_select 'changefreq' + assert_select 'priority' + end + end + should 'show location and last modified' do + assert_select 'loc', community_url(@community) + assert_select 'lastmod', @community.updated_at.to_s + end + end + +end diff --git a/test/fixtures/files/resourcesync.xsd b/test/fixtures/files/resourcesync.xsd new file mode 100644 index 000000000..c9da3f106 --- /dev/null +++ b/test/fixtures/files/resourcesync.xsd @@ -0,0 +1,147 @@ + + + + + +XML Schema for ResoureSync extensions to Sitemap files. +See: http://www.openarchives.org/rs/ . +2013-01-06 first stab [Simeon Warner], +2013-08-15 for v0.9.1, v1.0 [Simeon Warner], +2016-04-26 for v1.1 [Simeon Warner]. + + + + + + + Metadata about a resource + + + + + + + + + + + + + + + + + + + + + + + + + + + + Links to related resources + + + + + + + + + + + + + + + + + + + + + + + + Allowed values of the change attribute are + created, updated, deleted + + + + + + + + + + + + + Path values must start with a slash, must not end with a slash + + + + + + + + + + + MIME Content Types are described in RFCs 2045,2046 + http://tools.ietf.org/html/rfc2045 + http://tools.ietf.org/html/rfc2046 + + + + + + + + + + + The priority attribute may have values 1 through 999999 + + + + + + + + + + + + Syntax for link relation values is specified by + http://tools.ietf.org/html/rfc5988 and the relevant + portion is: + + # link-param = ( ( "rel" "=" relation-types ) + # ... + # relation-type = reg-rel-type | ext-rel-type + # reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) + # ext-rel-type = URI + # + + Registered link relations (reg-rel-type) are listed at + http://www.iana.org/assignments/link-relations/link-relations.xhtml + and URIs are allowed for extension (ext-rel-type) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/files/siteindex.xsd b/test/fixtures/files/siteindex.xsd new file mode 100644 index 000000000..0c62df45c --- /dev/null +++ b/test/fixtures/files/siteindex.xsd @@ -0,0 +1,72 @@ + + + + + XML Schema for Sitemap index files. + Last Modifed 2009-04-08 + + + + + + + Container for a set of up to 50,000 sitemap URLs. + This is the root element of the XML file. + + + + + + + + + + + + + + Container for the data needed to describe a sitemap. + + + + + + + + + + + + + REQUIRED: The location URI of a sitemap. + The URI must conform to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt). + + + + + + + + + + + + OPTIONAL: The date the document was last modified. The date must conform + to the W3C DATETIME format (http://www.w3.org/TR/NOTE-datetime). + Example: 2005-05-10 + Lastmod may also contain a timestamp. + Example: 2005-05-10T17:33:30+08:00 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/files/sitemap.xsd b/test/fixtures/files/sitemap.xsd new file mode 100644 index 000000000..74803479d --- /dev/null +++ b/test/fixtures/files/sitemap.xsd @@ -0,0 +1,114 @@ + + + + + + XML Schema for Sitemap files. + Last Modifed 2008-03-26 + + + + + + + Container for a set of up to 50,000 document elements. + This is the root element of the XML file. + + + + + + + + + + + + + + Container for the data needed to describe a document to crawl. + + + + + + + + + + + + + + + REQUIRED: The location URI of a document. + The URI must conform to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt). + + + + + + + + + + + + OPTIONAL: The date the document was last modified. The date must conform + to the W3C DATETIME format (http://www.w3.org/TR/NOTE-datetime). + Example: 2005-05-10 + Lastmod may also contain a timestamp. + Example: 2005-05-10T17:33:30+08:00 + + + + + + + + + + + + + + + + OPTIONAL: Indicates how frequently the content at a particular URL is + likely to change. The value "always" should be used to describe + documents that change each time they are accessed. The value "never" + should be used to describe archived URLs. Please note that web + crawlers may not necessarily crawl pages marked "always" more often. + Consider this element as a friendly suggestion and not a command. + + + + + + + + + + + + + + + + + OPTIONAL: The priority of a particular URL relative to other pages + on the same site. The value for this element is a number between + 0.0 and 1.0 where 0.0 identifies the lowest priority page(s). + The default priority of a page is 0.5. Priority is used to select + between pages on your site. Setting a priority of 1.0 for all URLs + will not help you, as the relative priority of pages on your site + is what will be considered. + + + + + + + + + diff --git a/test/models/item_test.rb b/test/models/item_test.rb index f0d111b14..bb4d6d14e 100644 --- a/test/models/item_test.rb +++ b/test/models/item_test.rb @@ -24,12 +24,16 @@ class ItemTest < ActiveSupport::TestCase unlocked_item.save! end assert item.valid? + refute_equal 0, Item.public.count + assert_equal item.id, Item.public.first.id + item.unlock_and_fetch_ldp_object(&:destroy) end test 'there is no default visibility' do item = Item.new_locked_ldp_object assert_nil item.visibility + assert_equal 0, Item.public.count end test 'unknown visibilities are not valid' do @@ -40,6 +44,7 @@ class ItemTest < ActiveSupport::TestCase end assert_not item.valid? + assert_equal 0, Item.public.count assert item.errors[:visibility].present? assert_includes item.errors[:visibility], 'some_fake_visibility is not a known visibility' end @@ -91,6 +96,7 @@ class ItemTest < ActiveSupport::TestCase end assert_not item.valid? + assert_equal 0, Item.public.count assert item.errors[:visibility_after_embargo].present? assert_includes item.errors[:visibility_after_embargo], "can't be blank" end diff --git a/test/models/jupiter_core/deferred_solr_query_test.rb b/test/models/jupiter_core/deferred_solr_query_test.rb index bbc25b492..adfc4f931 100644 --- a/test/models/jupiter_core/deferred_solr_query_test.rb +++ b/test/models/jupiter_core/deferred_solr_query_test.rb @@ -21,15 +21,22 @@ class DeferredSimpleSolrQueryTest < ActiveSupport::TestCase visibility: JupiterCore::VISIBILITY_PUBLIC) another_obj = @@klass.new_locked_ldp_object(title: 'zoo', owner: users(:regular).id, visibility: JupiterCore::VISIBILITY_PUBLIC) + private_obj = @@klass.new_locked_ldp_object(title: 'boo', owner: users(:regular).id, + visibility: JupiterCore::VISIBILITY_PRIVATE) obj.unlock_and_fetch_ldp_object(&:save!) another_obj.unlock_and_fetch_ldp_object(&:save!) + private_obj.unlock_and_fetch_ldp_object(&:save!) assert @@klass.all.present? - assert_equal @@klass.all.total_count, 2 + assert_equal @@klass.all.total_count, 3 assert @@klass.where(title: 'foo').first.id == obj.id - assert_equal @@klass.sort(:title, :desc).map(&:id), [another_obj.id, obj.id] + assert_equal @@klass.sort(:title, :desc).map(&:id), [another_obj.id, obj.id, private_obj.id] + + # visibility constraints + assert_equal 2, @@klass.where(visibility: JupiterCore::VISIBILITY_PUBLIC).count + assert_equal private_obj.id, @@klass.where(visibility: JupiterCore::VISIBILITY_PRIVATE).first.id end # regression test for #138 diff --git a/test/models/thesis_test.rb b/test/models/thesis_test.rb index 56eaf16df..b900434f5 100644 --- a/test/models/thesis_test.rb +++ b/test/models/thesis_test.rb @@ -32,12 +32,16 @@ class ThesisTest < ActiveSupport::TestCase assert_match(" <#{collection_uri}>", triples) end assert thesis.valid? + refute_equal 0, Thesis.public.count + assert_equal thesis.id, Thesis.public.first.id + thesis.unlock_and_fetch_ldp_object(&:destroy) end test 'there is no default visibility' do thesis = Thesis.new_locked_ldp_object assert_nil thesis.visibility + assert_equal 0, Thesis.public.count end test 'unknown visibilities are not valid' do @@ -48,6 +52,7 @@ class ThesisTest < ActiveSupport::TestCase end assert_not thesis.valid? + assert_equal 0, Thesis.public.count assert thesis.errors[:visibility].present? assert_includes thesis.errors[:visibility], 'some_fake_visibility is not a known visibility' end @@ -99,6 +104,7 @@ class ThesisTest < ActiveSupport::TestCase end assert_not thesis.valid? + assert_equal 0, Thesis.public.count assert thesis.errors[:visibility_after_embargo].present? assert_includes thesis.errors[:visibility_after_embargo], "can't be blank" end