diff --git a/README.md b/README.md index f36dc98e1..e6df52a9b 100644 --- a/README.md +++ b/README.md @@ -163,9 +163,9 @@ for more information. Events older then a month are archived to S3, you can import these events back into your local DB by running the rake tasks in lib/tasks/events.rake, after you set up the relavent ENV variables. For example if you want to find all the -events that are relavant for a particular content item you can run: +events that are relavant for a particular content_id you can run: ```sh -rake 'events:import_content_item_events[a796ca43-021b-4960-9c99-f41bb8ef2266]' +rake 'events:import_content_id_events[a796ca43-021b-4960-9c99-f41bb8ef2266]' ``` see the rake task for more details. diff --git a/app/commands/base_command.rb b/app/commands/base_command.rb index ced0d7fcc..6be914060 100644 --- a/app/commands/base_command.rb +++ b/app/commands/base_command.rb @@ -56,15 +56,16 @@ def self.raise_validation_command_error(e) end private_class_method :execute_callbacks, :raise_validation_command_error - def check_version_and_raise_if_conflicting(current_versioned_item, previous_version_number) - current_version = LockVersion.find_by(target: current_versioned_item) + def check_version_and_raise_if_conflicting(current_versioned_item, previous_version) + return unless current_versioned_item && previous_version.present? - return unless current_versioned_item && current_version + current_version = current_versioned_item.stale_lock_version - if current_version.conflicts_with?(previous_version_number) - friendly_message = "A lock-version conflict occurred. The `previous_version` you've sent " + - "(#{previous_version_number}) is not the same as the current " + - "lock version of the content item (#{current_version.number})." + if current_version != previous_version.to_i + friendly_message = "A lock-version conflict occurred. The " + + "`previous_version` you've sent (#{previous_version}) is not the " + + "same as the current lock version of the edition " + + "(#{current_version})." fields = { fields: { @@ -74,6 +75,7 @@ def check_version_and_raise_if_conflicting(current_versioned_item, previous_vers raise_command_error(409, "Conflict", fields, friendly_message: friendly_message) end + current_version end diff --git a/app/commands/v2/discard_draft.rb b/app/commands/v2/discard_draft.rb index a32cfe8cd..183862c6b 100644 --- a/app/commands/v2/discard_draft.rb +++ b/app/commands/v2/discard_draft.rb @@ -4,33 +4,37 @@ class DiscardDraft < BaseCommand def call raise_error_if_missing_draft! - check_version_and_raise_if_conflicting(draft, payload[:previous_version]) + check_version_and_raise_if_conflicting(document, payload[:previous_version]) - delete_supporting_objects + delete_supporting_objects(document.draft) delete_draft_from_database - increment_live_lock_version if live + increment_lock_version after_transaction_commit do - downstream_discard_draft(draft.base_path, draft.content_id, locale) + downstream_discard_draft( + document.draft.base_path, + document.content_id, + document.locale + ) end - Action.create_discard_draft_action(draft, locale, event) - Success.new(content_id: content_id) + Action.create_discard_draft_action(document.draft, document.locale, event) + Success.new(content_id: document.content_id) end private def raise_error_if_missing_draft! - return if draft.present? + return if document.draft.present? - code = live.present? ? 422 : 404 - message = "There is no draft content item to discard" + code = document.published_or_unpublished.present? ? 422 : 404 + message = "There is not a draft edition of this document to discard" raise CommandError.new(code: code, message: message) end def delete_draft_from_database - draft.destroy + document.draft.destroy end def downstream_discard_draft(path_used, content_id, locale) @@ -46,43 +50,26 @@ def downstream_discard_draft(path_used, content_id, locale) ) end - def delete_supporting_objects - Location.find_by(content_item: draft).try(:destroy) - State.find_by(content_item: draft).try(:destroy) - Translation.find_by(content_item: draft).try(:destroy) - UserFacingVersion.find_by(content_item: draft).try(:destroy) - LockVersion.find_by(target: draft).try(:destroy) - AccessLimit.find_by(content_item: draft).try(:destroy) - ChangeNote.where(content_item: draft).destroy_all + def delete_supporting_objects(edition) + Location.find_by(edition: edition).try(:destroy) + State.find_by(edition: edition).try(:destroy) + Translation.find_by(edition: edition).try(:destroy) + UserFacingVersion.find_by(edition: edition).try(:destroy) + LockVersion.find_by(target: edition).try(:destroy) + AccessLimit.find_by(edition: edition).try(:destroy) + ChangeNote.where(edition: edition).destroy_all end - def increment_live_lock_version - LockVersion.find_by!(target: live).increment! + def increment_lock_version + document.increment!(:stale_lock_version) end - def draft - @draft ||= ContentItem.find_by( - content_id: content_id, - locale: locale, - state: "draft", + def document + @document ||= Document.find_or_create_locked( + content_id: payload[:content_id], + locale: payload.fetch(:locale, Edition::DEFAULT_LOCALE), ) end - - def live - @live ||= ContentItem.find_by( - content_id: content_id, - locale: locale, - state: %w(published unpublished), - ) - end - - def content_id - payload[:content_id] - end - - def locale - payload[:locale] || ContentItem::DEFAULT_LOCALE - end end end end diff --git a/app/commands/v2/patch_link_set.rb b/app/commands/v2/patch_link_set.rb index caef93aea..4399f000f 100644 --- a/app/commands/v2/patch_link_set.rb +++ b/app/commands/v2/patch_link_set.rb @@ -4,10 +4,11 @@ class PatchLinkSet < BaseCommand def call raise_unless_links_hash_is_provided validate_schema - link_set = find_or_create_link_set(content_id) + link_set = LinkSet.find_or_create_locked(content_id: content_id) check_version_and_raise_if_conflicting(link_set, previous_version_number) - LockVersion.find_or_create_by!(target: link_set).increment! + + link_set.increment!(:stale_lock_version) grouped_links.each do |group, payload_content_ids| # For each set of links in a LinkSet scoped by link_type, this iterator @@ -32,23 +33,6 @@ def call private - def find_or_create_link_set(content_id) - begin - retries ||= 0 - LinkSet.transaction(requires_new: true) do - LinkSet.find_or_create_by!(content_id: content_id).lock! - end - rescue ActiveRecord::RecordNotUnique - # This should never need more than 1 retry as the scenario this error - # would occur is: inbetween rails find_or_create SELECT & INSERT - # queries a concurrent request ran an INSERT. Thus on retry the - # SELECT would succeed. - # So if this actually throws an exception here we probably have a - # weird underlying problem. - retry if (retries += 1) == 1 - end - end - def content_id payload.fetch(:content_id) end @@ -82,10 +66,10 @@ def raise_unless_links_hash_is_provided def send_downstream return unless downstream - draft_locales = Queries::LocalesForContentItems.call([content_id]) + draft_locales = Queries::LocalesForEditions.call([content_id], %w[draft live]) draft_locales.each { |(content_id, locale)| downstream_draft(content_id, locale) } - live_locales = Queries::LocalesForContentItems.call([content_id], %w[published unpublished]) + live_locales = Queries::LocalesForEditions.call([content_id], %w[live]) live_locales.each { |(content_id, locale)| downstream_live(content_id, locale) } end @@ -133,7 +117,7 @@ def schema_validator def schema_name @schema_name ||= Queries::GetLatest.( - ContentItem.where(content_id: payload[:content_id]) + Edition.with_document.where("documents.content_id": content_id) ).pluck(:schema_name).first end end diff --git a/app/commands/v2/post_action.rb b/app/commands/v2/post_action.rb index 41fa9f8df..53302a543 100644 --- a/app/commands/v2/post_action.rb +++ b/app/commands/v2/post_action.rb @@ -2,59 +2,51 @@ module Commands module V2 class PostAction < BaseCommand def call - check_version_and_raise_if_conflicting(content_item, previous_version_number) + check_version_and_raise_if_conflicting(document, previous_version_number) Action.create!( - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, action: action_type, - content_item: content_item, + edition: edition, user_uid: event.user_uid, event: event, ) Success.new( - { content_id: content_id, locale: locale, action: action_type }, + { content_id: document.content_id, locale: document.locale, action: action_type }, code: 201, ) end private - def content_id - payload.fetch(:content_id) - end - - def locale - payload.fetch(:locale, ContentItem::DEFAULT_LOCALE) + def document + @document ||= Document.find_or_create_locked( + content_id: payload.fetch(:content_id), + locale: payload.fetch(:locale, Edition::DEFAULT_LOCALE), + ) end def draft? payload[:draft].nil? ? true : payload[:draft] end - def content_item - @content_item ||= find_content_item + def edition + edition = draft? ? document.draft : document.published_or_unpublished + + unless edition + message = "Could not find an edition to associate this action with" + raise_command_error(404, message, fields: {}) + end + + edition end def action_type payload[:action] end - def find_content_item - content_item = ContentItem.find_by( - content_id: content_id, - locale: locale, - state: draft? ? %w(draft) : %w(published unpublished), - ) - - unless content_item - message = "Could not find a content item to associate this action with" - raise_command_error(404, message, fields: {}) - end - content_item - end - def previous_version_number payload[:previous_version].to_i if payload[:previous_version] end diff --git a/app/commands/v2/publish.rb b/app/commands/v2/publish.rb index 5bf20df70..052efa97f 100644 --- a/app/commands/v2/publish.rb +++ b/app/commands/v2/publish.rb @@ -4,10 +4,10 @@ class Publish < BaseCommand def call validate - publish_content_item + publish_edition after_transaction_commit do - send_downstream(content_item.content_id, content_item.locale, update_type) + send_downstream(document.content_id, document.locale, update_type) end Success.new(content_id: content_id) @@ -15,56 +15,60 @@ def call private - def publish_content_item + def publish_edition delete_change_notes unless update_type == "major" previous_item.supersede if previous_item - unless content_item.pathless? + unless edition.pathless? redirect_old_base_path clear_published_items_of_same_locale_and_base_path end set_public_updated_at set_first_published_at - content_item.publish + edition.publish remove_access_limit create_publish_action end def create_publish_action - Action.create_publish_action(content_item, locale, event) + Action.create_publish_action(edition, document.locale, event) end def remove_access_limit - AccessLimit.find_by(content_item: content_item).try(:destroy) + AccessLimit.find_by(edition: edition).try(:destroy) end def validate - no_draft_item_exists unless content_item + no_draft_item_exists unless edition validate_update_type - check_version_and_raise_if_conflicting(content_item, previous_version_number) + check_version_and_raise_if_conflicting(document, previous_version_number) end def update_type - @update_type ||= payload[:update_type] || content_item.update_type + @update_type ||= payload[:update_type] || edition.update_type + end + + def edition + document.draft end def previous_item - @previous_item ||= lookup_previous_item + document.published_or_unpublished end def redirect_old_base_path return unless previous_item previous_base_path = previous_item.base_path - if previous_base_path != content_item.base_path - publish_redirect(previous_base_path, content_item.locale) + if previous_base_path != edition.base_path + publish_redirect(previous_base_path, document.locale) end end def no_draft_item_exists if already_published? - message = "Cannot publish an already published content item" + message = "Cannot publish an already published edition" raise_command_error(400, message, fields: {}) else message = "Item with content_id #{content_id} and locale #{locale} does not exist" @@ -85,15 +89,22 @@ def validate_update_type end def delete_change_notes - ChangeNote.where(content_item: content_item).delete_all + ChangeNote.where(edition: edition).delete_all + end + + def document + @document ||= Document.find_or_create_locked( + content_id: payload[:content_id], + locale: payload.fetch(:locale, Edition::DEFAULT_LOCALE), + ) end def content_id - payload[:content_id] + document.content_id end def locale - payload.fetch(:locale, ContentItem::DEFAULT_LOCALE) + document.locale end def previous_version_number @@ -104,28 +115,17 @@ def valid_update_types %w(major minor republish links) end - def content_item - @content_item ||= ContentItem.find_by( - id: pessimistic_content_item_scope.pluck(:id), - state: "draft", - ) - end - def already_published? - ContentItem.exists?(content_id: content_id, locale: locale, state: "published") - end - - def pessimistic_content_item_scope - ContentItem.where(content_id: content_id, locale: locale).lock + document.editions.exists?(state: "published") end def clear_published_items_of_same_locale_and_base_path SubstitutionHelper.clear!( - new_item_document_type: content_item.document_type, - new_item_content_id: content_item.content_id, + new_item_document_type: edition.document_type, + new_item_content_id: document.content_id, state: "published", - locale: content_item.locale, - base_path: content_item.base_path, + locale: document.locale, + base_path: edition.base_path, downstream: downstream, callbacks: callbacks, nested: true, @@ -133,32 +133,32 @@ def clear_published_items_of_same_locale_and_base_path end def set_public_updated_at - return if content_item.public_updated_at.present? + return if edition.public_updated_at.present? if update_type == "major" - content_item.update_attributes!(public_updated_at: Time.zone.now) + edition.update_attributes!(public_updated_at: Time.zone.now) elsif update_type == "minor" - content_item.update_attributes!(public_updated_at: previous_item.public_updated_at) + edition.update_attributes!(public_updated_at: previous_item.public_updated_at) end end def set_first_published_at - return if content_item.first_published_at.present? - content_item.update_attributes!(first_published_at: Time.zone.now) + return if edition.first_published_at.present? + edition.update_attributes!(first_published_at: Time.zone.now) end def publish_redirect(previous_base_path, locale) - draft_redirect = ContentItem.find_by( + draft_redirect = Edition.with_document.find_by( state: "draft", - locale: locale, + "documents.locale": locale, base_path: previous_base_path, schema_name: "redirect", ) self.class.call( { - content_id: draft_redirect.content_id, - locale: locale, + content_id: draft_redirect.document.content_id, + locale: draft_redirect.document.locale, update_type: "major", }, downstream: downstream, @@ -167,20 +167,6 @@ def publish_redirect(previous_base_path, locale) ) if draft_redirect end - def lookup_previous_item - previous_items = ContentItem.where( - content_id: content_id, - locale: locale, - state: %w(published unpublished), - ) - - if previous_items.size > 1 - raise "There should only be one previous published or unpublished item" - end - - previous_items.order("content_id").first - end - def send_downstream(content_id, locale, update_type) return unless downstream diff --git a/app/commands/v2/put_content.rb b/app/commands/v2/put_content.rb index 49ed1730f..0f2c6ff3e 100644 --- a/app/commands/v2/put_content.rb +++ b/app/commands/v2/put_content.rb @@ -5,14 +5,21 @@ def call PutContentValidator.new(payload, self).validate prepare_content_with_base_path - content_item = create_or_update_content_item - update_content_dependencies(content_item) + edition = create_or_update_edition + update_content_dependencies(edition) after_transaction_commit do - send_downstream(content_item.content_id, locale) + send_downstream(document.content_id, document.locale) end - Success.new(present_response(content_item)) + Success.new(present_response(edition)) + end + + def document + @document ||= Document.find_or_create_locked( + content_id: payload.fetch(:content_id), + locale: payload.fetch(:locale, Edition::DEFAULT_LOCALE), + ) end private @@ -27,12 +34,12 @@ def prepare_content_with_base_path clear_draft_items_of_same_locale_and_base_path end - def update_content_dependencies(content_item) + def update_content_dependencies(edition) create_redirect - access_limit(content_item) - update_last_edited_at(content_item, payload[:last_edited_at]) - ChangeNote.create_from_content_item(payload, content_item) - Action.create_put_content_action(content_item, locale, event) + access_limit(edition) + update_last_edited_at(edition, payload[:last_edited_at]) + ChangeNote.create_from_edition(payload, edition) + Action.create_put_content_action(edition, document.locale, event) end def create_redirect @@ -42,56 +49,52 @@ def create_redirect payload, callbacks).create end - - def present_response(content_item) + def present_response(edition) Presenters::Queries::ContentItemPresenter.present( - content_item, + edition, include_warnings: true, ) end - def access_limit(content_item) + def access_limit(edition) if payload[:access_limited] && (users = payload[:access_limited][:users]) - AccessLimit.find_or_create_by(content_item: content_item).tap do |access_limit| + AccessLimit.find_or_create_by(edition: edition).tap do |access_limit| access_limit.update_attributes!(users: users) end else - AccessLimit.find_by(content_item: content_item).try(:destroy) + AccessLimit.find_by(edition: edition).try(:destroy) end end - def create_or_update_content_item + def create_or_update_edition if previously_drafted_item - updated_item, @previous_item = UpdateExistingDraftContentItem.new(previously_drafted_item, self, payload).call + updated_item, @previous_item = UpdateExistingDraftEdition.new(previously_drafted_item, self, payload).call else - new_draft_content_item = CreateDraftContentItem.new(self, payload, previously_published_item).call + new_draft_edition = CreateDraftEdition.new(self, payload, previously_published_item).call end - updated_item || new_draft_content_item + updated_item || new_draft_edition end def previously_published_item @previously_published_item ||= PreviouslyPublishedItem.new( - content_id, payload[:base_path], locale, self + document, payload[:base_path], self ).call end def base_path_required? - !ContentItem::EMPTY_BASE_PATH_FORMATS.include?(payload[:schema_name]) + !Edition::EMPTY_BASE_PATH_FORMATS.include?(payload[:schema_name]) end def previously_drafted_item - @previously_drafted_item ||= ContentItem.find_by( - id: pessimistic_content_item_scope.pluck(:id), - state: "draft", - ) + document.draft end def clear_draft_items_of_same_locale_and_base_path SubstitutionHelper.clear!( new_item_document_type: payload[:document_type], - new_item_content_id: content_id, + new_item_content_id: document.content_id, state: "draft", - locale: locale, + locale: document.locale, base_path: payload[:base_path], downstream: downstream, callbacks: callbacks, @@ -99,24 +102,12 @@ def clear_draft_items_of_same_locale_and_base_path ) end - def locale - payload.fetch(:locale, ContentItem::DEFAULT_LOCALE) - end - - def pessimistic_content_item_scope - ContentItem.where(content_id: content_id, locale: locale).lock - end - - def content_id - payload.fetch(:content_id) - end - - def update_last_edited_at(content_item, last_edited_at = nil) + def update_last_edited_at(edition, last_edited_at = nil) if last_edited_at.nil? && %w(major minor).include?(payload[:update_type]) last_edited_at = Time.zone.now end - content_item.update_attributes(last_edited_at: last_edited_at) if last_edited_at + edition.update_attributes(last_edited_at: last_edited_at) if last_edited_at end def bulk_publishing? diff --git a/app/commands/v2/represent_downstream.rb b/app/commands/v2/represent_downstream.rb index 986e988e0..0963149b4 100644 --- a/app/commands/v2/represent_downstream.rb +++ b/app/commands/v2/represent_downstream.rb @@ -7,11 +7,11 @@ def self.name def call(content_ids, draft = false) if draft - with_locales = Queries::LocalesForContentItems.call(content_ids, draft_states) + with_locales = Queries::LocalesForEditions.call(content_ids, %w[draft live]) with_locales.each { |(content_id, locale)| downstream_draft(content_id, locale) } end - with_locales = Queries::LocalesForContentItems.call(content_ids, live_states) + with_locales = Queries::LocalesForEditions.call(content_ids, %w[live]) with_locales.each_with_index do |(content_id, locale), index| sleep 60 if (index + 1) % 10_000 == 0 downstream_live(content_id, locale) @@ -20,14 +20,6 @@ def call(content_ids, draft = false) private - def draft_states - %w{draft published unpublished} - end - - def live_states - %w{published unpublished} - end - def downstream_draft(content_id, locale) event_payload = { content_id: content_id, diff --git a/app/commands/v2/unpublish.rb b/app/commands/v2/unpublish.rb index f5d6972d6..0faf97b68 100644 --- a/app/commands/v2/unpublish.rb +++ b/app/commands/v2/unpublish.rb @@ -3,17 +3,19 @@ module V2 class Unpublish < BaseCommand def call validate - previous_item.supersede if previous_item_should_be_superseded? + + previous.supersede if previous_edition_should_be_superseded? transition_state - AccessLimit.find_by(content_item: content_item).try(:destroy) + AccessLimit.find_by(edition: edition).try(:destroy) after_transaction_commit do send_downstream end - Action.create_unpublish_action(content_item, unpublishing_type, locale, event) + Action.create_unpublish_action(edition, unpublishing_type, + document.locale, event) - Success.new(content_id: content_id) + Success.new(content_id: document.content_id) end private @@ -36,12 +38,8 @@ def raise_invalid_unpublishing_type raise_command_error(422, message, fields: {}) end - def content_item - @content_item ||= find_unpublishable_content_item - end - - def content_id - @content_id ||= payload.fetch(:content_id) + def edition + @edition ||= find_unpublishable_edition end def validate_allow_discard_draft @@ -51,20 +49,20 @@ def validate_allow_discard_draft end end - def validate_content_item_presence - unless content_item.present? - message = "Could not find a content item to unpublish" + def validate_edition_presence + unless edition.present? + message = "Could not find an edition to unpublish" raise_command_error(404, message, fields: {}) end end def validate_draft_presence - if draft_exists? && !payload[:allow_draft] + if document.draft.present? && !payload[:allow_draft] if payload[:discard_drafts] == true DiscardDraft.call( { - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, }, downstream: downstream, callbacks: callbacks, @@ -79,13 +77,13 @@ def validate_draft_presence def validate validate_allow_discard_draft - validate_content_item_presence - check_version_and_raise_if_conflicting(content_item, previous_version_number) + validate_edition_presence + check_version_and_raise_if_conflicting(document, previous_version_number) validate_draft_presence end def unpublish - content_item.unpublish(payload.slice(:type, :explanation, :alternative_path, :unpublished_at)) + edition.unpublish(payload.slice(:type, :explanation, :alternative_path, :unpublished_at)) rescue ActiveRecord::RecordInvalid => e raise_command_error(422, e.message, fields: {}) end @@ -95,68 +93,46 @@ def send_downstream DownstreamDraftWorker.perform_async_in_queue( DownstreamDraftWorker::HIGH_QUEUE, - content_id: content_item.content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, payload_version: event.id, update_dependencies: true, ) DownstreamLiveWorker.perform_async_in_queue( DownstreamLiveWorker::HIGH_QUEUE, - content_id: content_item.content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, payload_version: event.id, update_dependencies: true, ) end - def locale - payload.fetch(:locale, ContentItem::DEFAULT_LOCALE) - end - def previous_version_number payload[:previous_version].to_i if payload[:previous_version] end - def find_unpublishable_content_item - allowed_states = %w(published unpublished) - + def find_unpublishable_edition if payload[:allow_draft] - allowed_states = %w(draft) + document.draft + elsif previous && !Unpublishing.is_substitute?(previous) + previous end - - content_item = ContentItem.where( - content_id: content_id, - locale: locale, - state: allowed_states - ).order(nil).lock.first - - content_item if content_item && (payload[:allow_draft] || !Unpublishing.is_substitute?(content_item)) end - def previous_item_should_be_superseded? - previous_item && find_unpublishable_content_item != previous_item + def previous + document.published_or_unpublished end - def previous_item - raise "There should only be one previous published or unpublished item" if previous_items.size > 1 - previous_items.first + def previous_edition_should_be_superseded? + previous && (find_unpublishable_edition != previous) end - def previous_items - @previous_items ||= ContentItem.where( - content_id: content_id, - locale: locale, - state: %w(published unpublished), - ).order(nil) - end - - def draft_exists? - ContentItem.where( - content_id: content_id, - locale: locale, - state: "draft", - ).exists? + def document + @document ||= Document.find_or_create_locked( + content_id: payload.fetch(:content_id), + locale: payload.fetch(:locale, Edition::DEFAULT_LOCALE), + ) end end end diff --git a/app/controllers/lookups_controller.rb b/app/controllers/lookups_controller.rb index 9b3dcde7a..ad01d4d6f 100644 --- a/app/controllers/lookups_controller.rb +++ b/app/controllers/lookups_controller.rb @@ -5,9 +5,9 @@ def by_base_path states = %w(published unpublished) base_paths = params.fetch(:base_paths) - base_paths_and_content_ids = ContentItem + base_paths_and_content_ids = Edition.with_document .where(state: states, base_path: base_paths) - .pluck(:base_path, :content_id) + .pluck(:base_path, 'documents.content_id') .uniq response = Hash[base_paths_and_content_ids] diff --git a/app/controllers/publish_intents_controller.rb b/app/controllers/publish_intents_controller.rb index 54171812d..060cc16da 100644 --- a/app/controllers/publish_intents_controller.rb +++ b/app/controllers/publish_intents_controller.rb @@ -4,7 +4,7 @@ def show end def create_or_update - response = Commands::PutPublishIntent.call(content_item) + response = Commands::PutPublishIntent.call(edition) render status: response.code, json: response end @@ -15,7 +15,7 @@ def destroy private - def content_item + def edition payload.merge(base_path: base_path) end end diff --git a/app/controllers/v2/content_items_controller.rb b/app/controllers/v2/content_items_controller.rb index 4fcc164e6..01da4e95c 100644 --- a/app/controllers/v2/content_items_controller.rb +++ b/app/controllers/v2/content_items_controller.rb @@ -31,28 +31,28 @@ def show end def put_content - response = Commands::V2::PutContent.call(content_item) + response = Commands::V2::PutContent.call(edition) render status: response.code, json: response end def publish - response = Commands::V2::Publish.call(content_item) + response = Commands::V2::Publish.call(edition) render status: response.code, json: response end def unpublish - response = Commands::V2::Unpublish.call(content_item) + response = Commands::V2::Unpublish.call(edition) render status: response.code, json: response end def discard_draft - response = Commands::V2::DiscardDraft.call(content_item) + response = Commands::V2::DiscardDraft.call(edition) render status: response.code, json: response end private - def content_item + def edition payload.merge(content_id: path_params[:content_id]) end diff --git a/app/models/access_limit.rb b/app/models/access_limit.rb index 1c8eeb556..d7339851a 100644 --- a/app/models/access_limit.rb +++ b/app/models/access_limit.rb @@ -1,5 +1,5 @@ class AccessLimit < ApplicationRecord - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" validate :user_uids_are_strings diff --git a/app/models/action.rb b/app/models/action.rb index 0f339a4e1..707791ca2 100644 --- a/app/models/action.rb +++ b/app/models/action.rb @@ -1,35 +1,35 @@ class Action < ActiveRecord::Base - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" belongs_to :link_set belongs_to :event - validate :one_of_content_item_link_set + validate :one_of_edition_link_set validates :action, presence: true - def self.create_put_content_action(content_item, locale, event) - create_publishing_action("PutContent", content_item, locale, event) + def self.create_put_content_action(edition, locale, event) + create_publishing_action("PutContent", edition, locale, event) end - def self.create_publish_action(content_item, locale, event) - create_publishing_action("Publish", content_item, locale, event) + def self.create_publish_action(edition, locale, event) + create_publishing_action("Publish", edition, locale, event) end - def self.create_unpublish_action(content_item, unpublishing_type, locale, event) + def self.create_unpublish_action(edition, unpublishing_type, locale, event) action = "Unpublish#{unpublishing_type.camelize}" - create_publishing_action(action, content_item, locale, event) + create_publishing_action(action, edition, locale, event) end - def self.create_discard_draft_action(content_item, locale, event) - create_publishing_action("DiscardDraft", content_item, locale, event) + def self.create_discard_draft_action(edition, locale, event) + create_publishing_action("DiscardDraft", edition, locale, event) end - def self.create_publishing_action(action, content_item, locale, event) + def self.create_publishing_action(action, edition, locale, event) create!( - content_id: content_item.content_id, + content_id: edition.document.content_id, locale: locale, action: action, user_uid: event.user_uid, - content_item: content_item, + edition: edition, event: event, ) end @@ -47,9 +47,9 @@ def self.create_patch_link_set_action(link_set, event) private - def one_of_content_item_link_set - if content_item_id && link_set_id || content_item && link_set - errors.add(:base, "can not be associated with both a content item and link set") + def one_of_edition_link_set + if content_item_id && link_set_id || edition && link_set + errors.add(:base, "can not be associated with both an edition and link set") end end end diff --git a/app/models/change_note.rb b/app/models/change_note.rb index 85a0d2f83..40682cf6b 100644 --- a/app/models/change_note.rb +++ b/app/models/change_note.rb @@ -1,25 +1,25 @@ class ChangeNote < ActiveRecord::Base - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" - def self.create_from_content_item(payload, content_item) - ChangeNoteFactory.new(payload, content_item).build + def self.create_from_edition(payload, edition) + ChangeNoteFactory.new(payload, edition).build end - def self.join_content_items(content_item_scope) - content_item_scope.joins( + def self.join_editions(edition_scope) + edition_scope.joins( "LEFT JOIN change_notes ON change_notes.content_item_id = content_items.id" ) end end class ChangeNoteFactory - def initialize(payload, content_item) + def initialize(payload, edition) @payload = payload - @content_item = content_item + @edition = edition end def build - return unless content_item.update_type == "major" + return unless edition.update_type == "major" create_from_top_level_change_note || create_from_details_hash_change_note || create_from_details_hash_change_history @@ -27,15 +27,15 @@ def build private - attr_reader :payload, :content_item + attr_reader :payload, :edition def create_from_top_level_change_note return unless change_note ChangeNote. - find_or_create_by!(content_item: content_item). + find_or_create_by!(edition: edition). update!( note: change_note, - content_id: content_item.content_id, + content_id: edition.document.content_id, public_timestamp: Time.zone.now ) end @@ -43,9 +43,9 @@ def create_from_top_level_change_note def create_from_details_hash_change_note return unless note ChangeNote.create!( - content_item: content_item, - content_id: content_item.content_id, - public_timestamp: content_item.updated_at, + edition: edition, + content_id: edition.document.content_id, + public_timestamp: edition.updated_at, note: note, ) end @@ -55,8 +55,8 @@ def create_from_details_hash_change_history history_element = change_history.max_by { |h| h[:public_timestamp] } ChangeNote.create!( history_element.merge( - content_item: content_item, - content_id: content_item.content_id + edition: edition, + content_id: edition.document.content_id ) ) end @@ -66,10 +66,10 @@ def change_note end def note - content_item.details[:change_note] + edition.details[:change_note] end def change_history - content_item.details[:change_history] + edition.details[:change_history] end end diff --git a/app/models/concerns/find_or_create_locked.rb b/app/models/concerns/find_or_create_locked.rb new file mode 100644 index 000000000..165337611 --- /dev/null +++ b/app/models/concerns/find_or_create_locked.rb @@ -0,0 +1,23 @@ +module FindOrCreateLocked + extend ActiveSupport::Concern + + class_methods do + def find_or_create_locked(params) + begin + retries ||= 0 + self.transaction(requires_new: true) do + entity = self.lock.find_by(params) + entity || self.create!(params).lock! + end + rescue ActiveRecord::RecordNotUnique + # This should never need more than 1 retry as the scenario this error + # would occur is: inbetween rails find_by and create SELECT & INSERT + # queries a concurrent request ran an INSERT. Thus on retry the + # SELECT would succeed. + # So if this actually throws an exception here we probably have a + # weird underlying problem. + (retries += 1) == 1 ? retry : raise + end + end + end +end diff --git a/app/models/content_item.rb b/app/models/content_item.rb deleted file mode 100644 index 8b2b81514..000000000 --- a/app/models/content_item.rb +++ /dev/null @@ -1,240 +0,0 @@ -class ContentItem < ApplicationRecord - include SymbolizeJSON - include DescriptionOverrides - - enum content_store: { - draft: 'draft', - live: 'live' - } - - DEFAULT_LOCALE = "en".freeze - - TOP_LEVEL_FIELDS = [ - :analytics_identifier, - :base_path, - :content_id, - :content_store, - :description, - :details, - :document_type, - :first_published_at, - :last_edited_at, - :locale, - :need_ids, - :phase, - :public_updated_at, - :publishing_app, - :redirects, - :rendering_app, - :routes, - :schema_name, - :state, - :title, - :user_facing_version, - :update_type, - ].freeze - - NON_RENDERABLE_FORMATS = %w(redirect gone).freeze - EMPTY_BASE_PATH_FORMATS = %w(contact government).freeze - - scope :renderable_content, -> { where.not(document_type: NON_RENDERABLE_FORMATS) } - - belongs_to :document - - validates :schema_name, presence: true - validates :document_type, presence: true - - validates :base_path, absolute_path: true, if: :base_path_present? - validates :content_id, presence: true, uuid: true - validates :publishing_app, presence: true - validates :title, presence: true, if: :renderable_content? - validates :rendering_app, presence: true, dns_hostname: true, if: :requires_rendering_app? - validates :phase, inclusion: { - in: %w(alpha beta live), - message: 'must be either alpha, beta, or live' - } - validates :description, well_formed_content_types: { must_include: "text/html" } - validates :details, well_formed_content_types: { must_include_one_of: %w(text/html text/govspeak) } - - validates :locale, inclusion: { - in: I18n.available_locales.map(&:to_s), - message: 'must be a supported locale' - } - - validate :user_facing_version_must_increase - validate :draft_cannot_be_behind_live - - validates_with VersionForLocaleValidator - validates_with BasePathForStateValidator - validates_with StateForLocaleValidator - validates_with RoutesAndRedirectsValidator - - # Temporary code until we kill Location, State, Translation, and - # UserFacing Version - after_save do - if changes[:base_path] && changes[:base_path].last - Location.find_or_initialize_by(content_item_id: id) - .update!(base_path: changes[:base_path].last) - end - - if changes[:base_path] && !changes[:base_path].last - Location.find_by(content_item_id: id).try(:destroy) - end - - if changes[:state] - State.find_or_initialize_by(content_item_id: id) - .update!(name: changes[:state].last) - end - - if changes[:locale] - Translation.find_or_initialize_by(content_item_id: id) - .update!(locale: changes[:locale].last) - end - - if changes[:user_facing_version] - UserFacingVersion.find_or_initialize_by(content_item_id: id) - .update!(number: changes[:user_facing_version].last) - end - end - - before_save { ensure_document } - - def document_requires_updating? - !document || document.locale != locale || document.content_id != content_id - end - - def ensure_document - if document_requires_updating? - self.document = Document.find_or_create_by(content_id: content_id, - locale: locale) - end - end - - def requires_base_path? - EMPTY_BASE_PATH_FORMATS.exclude?(document_type) - end - - def pathless? - !self.requires_base_path? && !base_path - end - - def base_path_present? - base_path.present? - end - - def draft_cannot_be_behind_live - if state == "draft" - draft_version = user_facing_version - live_version = ContentItem.where( - content_id: content_id, - locale: locale, - state: %w(published unpublished), - ).pluck(:user_facing_version).first - end - - if %w(published unpublished).include?(state) - draft_version = ContentItem.where( - content_id: content_id, - locale: locale, - state: "draft", - ).pluck(:user_facing_version).first - live_version = user_facing_version - end - - return unless draft_version && live_version - - if draft_version < live_version - mismatch = "(#{draft_version} < #{live_version})" - message = "draft content item cannot be behind the live content item #{mismatch}" - errors.add(:user_facing_version, message) - end - end - - def user_facing_version_must_increase - return unless persisted? - return unless user_facing_version_changed? && user_facing_version <= user_facing_version_was - - mismatch = "(#{user_facing_version} <= #{user_facing_version_was})" - message = "cannot be less than or equal to the previous user_facing_version #{mismatch}" - errors.add(:user_facing_version, message) - end - - # FIXME: This method is used to retrieve a version of .details that doesn't - # have text/html, thus this can be used to convert the item to HTML - # It is here for comparing our Govspeak output with that that was provided to - # us previously and can be removed once we have migrated most applications. - def details_for_govspeak_conversion - return details unless details.is_a?(Hash) - - value_without_html = lambda do |value| - wrapped = Array.wrap(value) - html = wrapped.find { |item| item.is_a?(Hash) && item[:content_type] == "text/html" } - govspeak = wrapped.find { |item| item.is_a?(Hash) && item[:content_type] == "text/govspeak" } - if html.present? && govspeak.present? - wrapped - [html] - else - value - end - end - - details.deep_dup.each_with_object({}) do |(key, value), memo| - memo[key] = value_without_html.call(value) - end - end - - def publish - update_attributes!(state: "published", content_store: "live") - end - - def supersede - update_attributes!(state: "superseded", content_store: nil) - end - - def unpublish(type:, explanation: nil, alternative_path: nil, unpublished_at: nil) - content_store = type == "substitute" ? nil : "live" - update_attributes!(state: "unpublished", content_store: content_store) - - unpublishing = Unpublishing.find_by(content_item: self) - - unpublished_at = nil unless type == "withdrawal" - - if unpublishing.present? - unpublishing.update_attributes( - type: type, - explanation: explanation, - alternative_path: alternative_path, - unpublished_at: unpublished_at, - ) - unpublishing - else - Unpublishing.create!( - content_item: self, - type: type, - explanation: explanation, - alternative_path: alternative_path, - unpublished_at: unpublished_at, - ) - end - end - - def substitute - unpublish( - type: "substitute", - explanation: "Automatically unpublished to make way for another content item", - ) - end - - def lock_version_number - LockVersion.find_by!(target: self).number - end - -private - - def renderable_content? - NON_RENDERABLE_FORMATS.exclude?(document_type) - end - - def requires_rendering_app? - renderable_content? && document_type != "contact" - end -end diff --git a/app/models/create_draft_content_item.rb b/app/models/create_draft_content_item.rb deleted file mode 100644 index d6d647371..000000000 --- a/app/models/create_draft_content_item.rb +++ /dev/null @@ -1,67 +0,0 @@ -class CreateDraftContentItem - def initialize(put_content, payload, previously_published_item) - @put_content = put_content - @payload = payload - @previously_published_item = previously_published_item - end - - def call - content_item.tap do - fill_out_new_content_item - end - end - -private - - attr_reader :payload, :put_content, :previously_published_item - - def content_item - @content_item ||= create_content_item - end - - def create_content_item - attributes = content_item_attributes_from_payload.merge( - locale: locale, - state: "draft", - content_store: "draft", - user_facing_version: user_facing_version_number_for_new_draft, - ) - ContentItem.create!(attributes) - end - - def lock_version_number_for_new_draft - previously_published_item.lock_version_number - end - - def user_facing_version_number_for_new_draft - previously_published_item.user_facing_version - end - - def locale - payload.fetch(:locale, ContentItem::DEFAULT_LOCALE) - end - - def fill_out_new_content_item - LockVersion.create!(target: content_item, number: lock_version_number_for_new_draft) - ensure_link_set_exists - - set_first_published_at - end - - def ensure_link_set_exists - link_set = LinkSet.find_or_create_by!(content_id: content_item.content_id) - LockVersion.find_or_create_by!(target: link_set, number: 1) - end - - def set_first_published_at - return unless previously_published_item.set_first_published_at? - return if content_item.first_published_at - content_item.update_attributes( - first_published_at: previously_published_item.first_published_at, - ) - end - - def content_item_attributes_from_payload - payload.slice(*ContentItem::TOP_LEVEL_FIELDS) - end -end diff --git a/app/models/create_draft_edition.rb b/app/models/create_draft_edition.rb new file mode 100644 index 000000000..8f7b77782 --- /dev/null +++ b/app/models/create_draft_edition.rb @@ -0,0 +1,64 @@ +class CreateDraftEdition + def initialize(put_content, payload, previously_published_item) + @put_content = put_content + @payload = payload + @previously_published_item = previously_published_item + end + + def call + edition.tap do + fill_out_new_edition + end + end + +private + + attr_reader :payload, :put_content, :previously_published_item + + def edition + @edition ||= create_edition + end + + def create_edition + attributes = edition_attributes_from_payload.merge( + state: "draft", + content_store: "draft", + user_facing_version: user_facing_version_number_for_new_draft, + ) + document.editions.create!(attributes) + end + + def user_facing_version_number_for_new_draft + previously_published_item.user_facing_version + end + + def document + put_content.document + end + + def fill_out_new_edition + document.increment!(:stale_lock_version) + ensure_link_set_exists + + set_first_published_at + end + + def ensure_link_set_exists + link_set = LinkSet.find_or_create_by!(content_id: document.content_id) + if link_set.stale_lock_version == 0 + link_set.increment!(:stale_lock_version) + end + end + + def set_first_published_at + return unless previously_published_item.set_first_published_at? + return if edition.first_published_at + edition.update_attributes( + first_published_at: previously_published_item.first_published_at, + ) + end + + def edition_attributes_from_payload + payload.slice(*Edition::TOP_LEVEL_FIELDS) + end +end diff --git a/app/models/document.rb b/app/models/document.rb index da4f10dd1..a60ed3013 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -1,7 +1,16 @@ class Document < ApplicationRecord - has_many :content_items + include FindOrCreateLocked - validates :content_id, presence: true + has_many :editions + has_one :draft, -> { where(content_store: "draft") }, class_name: Edition + has_one :live, -> { where(content_store: "live") }, class_name: Edition + + # Due to the scenario of unpublished type substitute we need a scope that + # can access published / unpublished which isn't tied to live content store + # FIXME - we should make this go away + has_one :published_or_unpublished, -> { where(state: %w(published unpublished)) }, class_name: Edition + + validates :content_id, presence: true, uuid: true validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s), diff --git a/app/models/edition.rb b/app/models/edition.rb index c614167c3..dd1e67bec 100644 --- a/app/models/edition.rb +++ b/app/models/edition.rb @@ -1,4 +1,272 @@ -# Needed to work around the fact that we use a polymorphic type and to avoid -# including a migration which changed the class name in the database, we simply -# make sure it is available under both names -Edition = ContentItem +class Edition < ApplicationRecord + self.table_name = "content_items" + + include SymbolizeJSON + include DescriptionOverrides + + enum content_store: { + draft: 'draft', + live: 'live' + } + + DEFAULT_LOCALE = "en".freeze + + TOP_LEVEL_FIELDS = [ + :analytics_identifier, + :base_path, + :content_store, + :description, + :details, + :document_type, + :first_published_at, + :last_edited_at, + :need_ids, + :phase, + :public_updated_at, + :publishing_app, + :redirects, + :rendering_app, + :routes, + :schema_name, + :state, + :title, + :user_facing_version, + :update_type, + ].freeze + + NON_RENDERABLE_FORMATS = %w(redirect gone).freeze + EMPTY_BASE_PATH_FORMATS = %w(contact government).freeze + + belongs_to :document + has_one :unpublishing, foreign_key: "content_item_id" + + scope :renderable_content, -> { where.not(document_type: NON_RENDERABLE_FORMATS) } + scope :with_document, -> { joins(:document) } + + validates :document, presence: true + + validates :schema_name, presence: true + validates :document_type, presence: true + + validates :base_path, absolute_path: true, if: :base_path_present? + validates :publishing_app, presence: true + validates :title, presence: true, if: :renderable_content? + validates :rendering_app, presence: true, dns_hostname: true, if: :requires_rendering_app? + validates :phase, inclusion: { + in: %w(alpha beta live), + message: 'must be either alpha, beta, or live' + } + validates :description, well_formed_content_types: { must_include: "text/html" } + validates :details, well_formed_content_types: { must_include_one_of: %w(text/html text/govspeak) } + + validate :user_facing_version_must_increase + validate :draft_cannot_be_behind_live + + validates_with VersionForDocumentValidator + validates_with BasePathForStateValidator + validates_with StateForDocumentValidator + validates_with RoutesAndRedirectsValidator + + # Temporary code until we remove content_id and locale fields + before_save do + next unless document + self.content_id = document.content_id unless content_id == document.content_id + self.locale = document.locale unless locale == document.locale + end + + after_save do + lock_version = LockVersion.find_or_create_by(target: self) + if document.stale_lock_version < lock_version.number + lock_version.update! number: document.stale_lock_version + end + end + + # Temporary code until we kill Location, State, Translation, and + # UserFacingVersion + after_save do + if changes[:base_path] && changes[:base_path].last + Location.find_or_initialize_by(content_item_id: id) + .update!(base_path: changes[:base_path].last) + end + + if changes[:base_path] && !changes[:base_path].last + Location.find_by(content_item_id: id).try(:destroy) + end + + if changes[:state] + State.find_or_initialize_by(content_item_id: id) + .update!(name: changes[:state].last) + end + + if changes[:locale] + Translation.find_or_initialize_by(content_item_id: id) + .update!(locale: changes[:locale].last) + end + + if changes[:user_facing_version] + UserFacingVersion.find_or_initialize_by(content_item_id: id) + .update!(number: changes[:user_facing_version].last) + end + end + + before_save { ensure_document } + + def document_requires_updating? + !document || document.locale != locale || document.content_id != content_id + end + + def ensure_document + if document_requires_updating? + self.document = Document.find_or_create_by(content_id: content_id, + locale: locale) + end + end + + def as_json(options = {}) + %i[content_id locale].each do |field| + next if Array.wrap(options[:methods]).include?(field) + only = Array.wrap(options[:only]) || [] + except = Array.wrap(options[:except]) || [] + if (only.empty? || only.include?(field)) && (except.empty? || !except.include?(field)) + options[:methods] = (options[:methods] || []) + [field] + end + end + super(options) + end + + def requires_base_path? + EMPTY_BASE_PATH_FORMATS.exclude?(document_type) + end + + def pathless? + !self.requires_base_path? && !base_path + end + + def base_path_present? + base_path.present? + end + + def draft_cannot_be_behind_live + return unless document + + if state == "draft" + draft_version = user_facing_version + published_unpublished_version = document.published_or_unpublished.try(:user_facing_version) + end + + if %w(published unpublished).include?(state) + draft_version = document.draft.user_facing_version if document.draft + published_unpublished_version = user_facing_version + end + + return unless draft_version && published_unpublished_version + + if draft_version < published_unpublished_version + mismatch = "(#{draft_version} < #{published_unpublished_version})" + message = "draft edition cannot be behind the published/unpublished edition #{mismatch}" + errors.add(:user_facing_version, message) + end + end + + def user_facing_version_must_increase + return unless persisted? + return unless user_facing_version_changed? && user_facing_version <= user_facing_version_was + + mismatch = "(#{user_facing_version} <= #{user_facing_version_was})" + message = "cannot be less than or equal to the previous user_facing_version #{mismatch}" + errors.add(:user_facing_version, message) + end + + # FIXME: This method is used to retrieve a version of .details that doesn't + # have text/html, thus this can be used to convert the item to HTML + # It is here for comparing our Govspeak output with that that was provided to + # us previously and can be removed once we have migrated most applications. + def details_for_govspeak_conversion + return details unless details.is_a?(Hash) + + value_without_html = lambda do |value| + wrapped = Array.wrap(value) + html = wrapped.find { |item| item.is_a?(Hash) && item[:content_type] == "text/html" } + govspeak = wrapped.find { |item| item.is_a?(Hash) && item[:content_type] == "text/govspeak" } + if html.present? && govspeak.present? + wrapped - [html] + else + value + end + end + + details.deep_dup.each_with_object({}) do |(key, value), memo| + memo[key] = value_without_html.call(value) + end + end + + def publish + update_attributes!(state: "published", content_store: "live") + end + + def supersede + update_attributes!(state: "superseded", content_store: nil) + end + + def unpublish(type:, explanation: nil, alternative_path: nil, unpublished_at: nil) + content_store = type == "substitute" ? nil : "live" + update_attributes!(state: "unpublished", content_store: content_store) + + unpublishing = Unpublishing.find_by(edition: self) + + unpublished_at = nil unless type == "withdrawal" + + if unpublishing.present? + unpublishing.update_attributes( + type: type, + explanation: explanation, + alternative_path: alternative_path, + unpublished_at: unpublished_at, + ) + unpublishing + else + Unpublishing.create!( + edition: self, + type: type, + explanation: explanation, + alternative_path: alternative_path, + unpublished_at: unpublished_at, + ) + end + end + + def substitute + unpublish( + type: "substitute", + explanation: "Automatically unpublished to make way for another document", + ) + end + +private + + # These are private whilst they are legacy attributes, that are currently + # kept for easy rollback to a previous iteration of the codebase + def content_id + self[:content_id] + end + + def content_id=(val) + write_attribute(:content_id, val) + end + + def locale + self[:locale] + end + + def locale=(val) + write_attribute(:locale, val) + end + + def renderable_content? + NON_RENDERABLE_FORMATS.exclude?(document_type) + end + + def requires_rendering_app? + renderable_content? && document_type != "contact" + end +end diff --git a/app/models/link.rb b/app/models/link.rb index b4df354e0..43a27e26b 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -6,9 +6,9 @@ class Link < ApplicationRecord validate :link_type_is_valid validate :content_id_is_valid - def self.filter_content_items(scope, filters) + def self.filter_editions(scope, filters) join_sql = <<-SQL.strip_heredoc - INNER JOIN link_sets ON link_sets.content_id = content_items.content_id + INNER JOIN link_sets ON link_sets.content_id = documents.content_id INNER JOIN links ON links.link_set_id = link_sets.id SQL diff --git a/app/models/link_set.rb b/app/models/link_set.rb index 77b05102e..c4bcbef6a 100644 --- a/app/models/link_set.rb +++ b/app/models/link_set.rb @@ -1,3 +1,10 @@ class LinkSet < ApplicationRecord + include FindOrCreateLocked + has_many :links, -> { order(link_type: :asc, position: :asc) }, dependent: :destroy + + after_save do + lock_version = LockVersion.find_or_create_by(target: self) + lock_version.update! number: stale_lock_version + end end diff --git a/app/models/location.rb b/app/models/location.rb index a18030d26..68d40d316 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -1,15 +1,12 @@ class Location < ApplicationRecord - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" - def self.filter(content_item_scope, base_path:) - join_content_items(content_item_scope) - .where("locations.base_path" => base_path) + def self.filter(edition_scope, base_path:) + join_editions(edition_scope).where("locations.base_path" => base_path) end - def self.join_content_items(content_item_scope) - content_item_scope.joins( - "INNER JOIN locations ON locations.content_item_id = content_items.id" - ) + def self.join_editions(edition_scope) + edition_scope.joins("INNER JOIN locations ON locations.content_item_id = content_items.id") end private diff --git a/app/models/lock_version.rb b/app/models/lock_version.rb index 727ca4a71..ee3d660fe 100644 --- a/app/models/lock_version.rb +++ b/app/models/lock_version.rb @@ -8,10 +8,10 @@ class LockVersion < ApplicationRecord item.update_column(:stale_lock_version, number) if number > (item.stale_lock_version || -1) end - def self.join_content_items(content_item_scope) - content_item_scope.joins( + def self.join_editions(edition_scope) + edition_scope.joins( "INNER JOIN lock_versions ON - lock_versions.target_id = content_items.id AND + lock_versions.target_id = editions.id AND lock_versions.target_type = 'ContentItem'" ) end @@ -23,7 +23,7 @@ def conflicts_with?(previous_version_number) end def lock_version_target - if content_item_target? + if edition_target? target.document else target @@ -41,7 +41,8 @@ def increment! private - def content_item_target? + def edition_target? + # The 'Edition' class used to be called the 'ContentItem' class. %w(ContentItem Edition).include? target_type end diff --git a/app/models/previously_published_item.rb b/app/models/previously_published_item.rb index 27cd5aa8d..b70f4fbc5 100644 --- a/app/models/previously_published_item.rb +++ b/app/models/previously_published_item.rb @@ -1,8 +1,7 @@ class PreviouslyPublishedItem - def initialize(content_id, base_path, locale, put_content) - @content_id = content_id + def initialize(document, base_path, put_content) + @document = document @base_path = base_path - @locale = locale @put_content = put_content end @@ -10,17 +9,10 @@ def call previously_published_item ? self : NoPreviousPublishedItem.new end - attr_reader :content_id, :base_path, :locale, :put_content + attr_reader :document, :base_path, :put_content def previously_published_item - @previously_published_item ||= - ContentItem.find_by(content_id: content_id, - state: %w(published unpublished), - locale: locale) - end - - def lock_version_number - previously_published_item.lock_version_number + 1 + document.published_or_unpublished end def user_facing_version @@ -48,10 +40,6 @@ def path_has_changed? end class NoPreviousPublishedItem - def lock_version_number - 1 - end - def user_facing_version 1 end diff --git a/app/models/state.rb b/app/models/state.rb index f9d79f4e3..fd4b45ddb 100644 --- a/app/models/state.rb +++ b/app/models/state.rb @@ -1,14 +1,11 @@ class State < ApplicationRecord - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" - def self.filter(content_item_scope, name:) - join_content_items(content_item_scope) - .where("states.name" => name) + def self.filter(edition_scope, name:) + join_editions(edition_scope).where("states.name" => name) end - def self.join_content_items(content_item_scope) - content_item_scope.joins( - "INNER JOIN states ON states.content_item_id = content_items.id" - ) + def self.join_editions(edition_scope) + edition_scope.joins("INNER JOIN states ON states.content_item_id = content_items.id") end end diff --git a/app/models/translation.rb b/app/models/translation.rb index 06b59f072..35b304ba7 100644 --- a/app/models/translation.rb +++ b/app/models/translation.rb @@ -1,14 +1,11 @@ class Translation < ApplicationRecord - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" - def self.filter(content_item_scope, locale:) - join_content_items(content_item_scope) - .where("translations.locale" => locale) + def self.filter(edition_scope, locale:) + join_editions(edition_scope).where("translations.locale" => locale) end - def self.join_content_items(content_item_scope) - content_item_scope.joins( - "INNER JOIN translations ON translations.content_item_id = content_items.id" - ) + def self.join_editions(edition_scope) + edition_scope.joins("INNER JOIN translations ON translations.content_item_id = content_items.id") end end diff --git a/app/models/unpublishing.rb b/app/models/unpublishing.rb index 8ce96a683..31ffc1ff3 100644 --- a/app/models/unpublishing.rb +++ b/app/models/unpublishing.rb @@ -1,7 +1,7 @@ class Unpublishing < ApplicationRecord self.inheritance_column = nil - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" VALID_TYPES = %w( gone @@ -11,7 +11,7 @@ class Unpublishing < ApplicationRecord withdrawal ).freeze - validates :content_item, presence: true, uniqueness: true + validates :edition, presence: true, uniqueness: true validates :type, presence: true, inclusion: { in: VALID_TYPES } validates :explanation, presence: true, if: :withdrawal? validates :alternative_path, presence: true, if: :redirect? @@ -25,7 +25,7 @@ def redirect? type == "redirect" end - def self.is_substitute?(content_item) - where(content_item: content_item).pluck(:type).first == "substitute" + def self.is_substitute?(edition) + where(edition: edition).pluck(:type).first == "substitute" end end diff --git a/app/models/update_existing_draft_content_item.rb b/app/models/update_existing_draft_content_item.rb deleted file mode 100644 index 4c01f5d69..000000000 --- a/app/models/update_existing_draft_content_item.rb +++ /dev/null @@ -1,59 +0,0 @@ -class UpdateExistingDraftContentItem - ATTRIBUTES_PROTECTED_FROM_RESET = [ - :id, - :created_at, - :updated_at, - :first_published_at, - :last_edited_at, - ].freeze - - attr_reader :payload, :put_content, :content_item - - def initialize(content_item, put_content, payload) - @content_item = content_item - @put_content = put_content - @payload = payload - end - - def call - update_lock_version - update_content_item - end - -private - - def update_lock_version - version = put_content.send(:check_version_and_raise_if_conflicting, content_item, payload[:previous_version]) - version.increment! - end - - def update_content_item - old_item = content_item.dup - assign_attributes_with_defaults - content_item.save! - [content_item, old_item] - end - - def assign_attributes_with_defaults - content_item.assign_attributes(new_attributes) - end - - def new_attributes - content_item.class.column_defaults.symbolize_keys - .merge(attributes.symbolize_keys) - .except(*ATTRIBUTES_PROTECTED_FROM_RESET) - end - - def attributes - content_item_attributes_from_payload.merge( - locale: payload.fetch(:locale, ContentItem::DEFAULT_LOCALE), - state: "draft", - content_store: "draft", - user_facing_version: content_item.user_facing_version, - ) - end - - def content_item_attributes_from_payload - payload.slice(*ContentItem::TOP_LEVEL_FIELDS) - end -end diff --git a/app/models/update_existing_draft_edition.rb b/app/models/update_existing_draft_edition.rb new file mode 100644 index 000000000..3c9dc83e4 --- /dev/null +++ b/app/models/update_existing_draft_edition.rb @@ -0,0 +1,65 @@ +class UpdateExistingDraftEdition + ATTRIBUTES_PROTECTED_FROM_RESET = [ + :id, + :document_id, + :content_id, + :locale, + :created_at, + :updated_at, + :first_published_at, + :last_edited_at, + ].freeze + + attr_reader :payload, :put_content, :edition + + def initialize(edition, put_content, payload) + @edition = edition + @put_content = put_content + @payload = payload + end + + def call + update_lock_version + update_edition + end + +private + + def update_lock_version + put_content.send(:check_version_and_raise_if_conflicting, document, payload[:previous_version]) + document.increment!(:stale_lock_version) + end + + def document + put_content.document + end + + def update_edition + old_edition = edition.dup + assign_attributes_with_defaults + edition.save! + [edition, old_edition] + end + + def assign_attributes_with_defaults + edition.assign_attributes(new_attributes) + end + + def new_attributes + edition.class.column_defaults.symbolize_keys + .merge(attributes.symbolize_keys) + .except(*ATTRIBUTES_PROTECTED_FROM_RESET) + end + + def attributes + edition_attributes_from_payload.merge( + state: "draft", + content_store: "draft", + user_facing_version: edition.user_facing_version, + ) + end + + def edition_attributes_from_payload + payload.slice(*Edition::TOP_LEVEL_FIELDS) + end +end diff --git a/app/models/user_facing_version.rb b/app/models/user_facing_version.rb index 09e8443f7..90bdfe7cb 100644 --- a/app/models/user_facing_version.rb +++ b/app/models/user_facing_version.rb @@ -1,24 +1,23 @@ class UserFacingVersion < ApplicationRecord - belongs_to :content_item + belongs_to :edition, foreign_key: "content_item_id" - def self.filter(content_item_scope, number:) - join_content_items(content_item_scope) - .where("user_facing_versions.number" => number) + def self.filter(edition_scope, number:) + join_editions(edition_scope).where("user_facing_versions.number" => number) end - def self.join_content_items(content_item_scope) - content_item_scope.joins( + def self.join_editions(edition_scope) + edition_scope.joins( "INNER JOIN user_facing_versions ON user_facing_versions.content_item_id = content_items.id" ) end private - def content_item_target? + def edition_target? true end def target - content_item + edition end end diff --git a/app/models/web_content_item.rb b/app/models/web_content_item.rb index cd3771e1d..9d573f65d 100644 --- a/app/models/web_content_item.rb +++ b/app/models/web_content_item.rb @@ -1,12 +1,14 @@ fields = %i{ id analytics_identifier + base_path content_id description details document_type first_published_at last_edited_at + locale need_ids phase public_updated_at @@ -14,14 +16,12 @@ redirects rendering_app routes + state schema_name title + unpublishing_type update_type - base_path - locale - state user_facing_version - unpublishing_type } WebContentItem = Struct.new(*fields) do @@ -61,4 +61,8 @@ def web_url def description self[:description]["value"] end + + def document + Document.find_by(content_id: content_id, locale: locale) + end end diff --git a/app/presenters/change_history_presenter.rb b/app/presenters/change_history_presenter.rb index bc29ffd58..c293a96d7 100644 --- a/app/presenters/change_history_presenter.rb +++ b/app/presenters/change_history_presenter.rb @@ -1,9 +1,9 @@ module Presenters class ChangeHistoryPresenter - attr_reader :content_item + attr_reader :web_content_item - def initialize(content_item) - @content_item = content_item + def initialize(web_content_item) + @web_content_item = web_content_item end def change_history @@ -13,12 +13,12 @@ def change_history private def details - SymbolizeJSON.symbolize(content_item.details) + SymbolizeJSON.symbolize(web_content_item.details) end def change_notes_for_content_item change_notes = ChangeNote - .where(content_id: content_item.content_id) + .where(content_id: content_id) .where("content_item_id IS NULL OR content_item_id IN (?)", content_item_ids) .order(:public_timestamp) .pluck(:note, :public_timestamp) @@ -27,13 +27,18 @@ def change_notes_for_content_item end def content_item_ids - ContentItem.where(content_id: content_item.content_id) - .where("user_facing_version <= ?", version_number) - .pluck(:id) + Edition.with_document + .where("documents.content_id": content_id) + .where("user_facing_version <= ?", version_number) + .pluck(:id) end def version_number - content_item.user_facing_version + web_content_item.user_facing_version + end + + def content_id + web_content_item.content_id end end end diff --git a/app/presenters/debug_presenter.rb b/app/presenters/debug_presenter.rb index bcce395c9..c6ed7f98a 100644 --- a/app/presenters/debug_presenter.rb +++ b/app/presenters/debug_presenter.rb @@ -10,20 +10,20 @@ def initialize(content_id) @content_id = content_id end - def content_items - @content_items ||= ContentItem.where(content_id: content_id) + def editions + @editions ||= Edition.with_document.where("documents.content_id": content_id) end def user_facing_versions - content_items.map(&:user_facing_version).sort.reverse + editions.map(&:user_facing_version).sort.reverse end - def latest_content_items - @latest_content_items ||= ::Queries::GetLatest.call(content_items) + def latest_editions + @latest_editions ||= ::Queries::GetLatest.call(editions) end def latest_state_with_locale - latest_content_items.map { |ci| [ci.locale, ci.state] } + latest_editions.map { |ci| [ci.document.locale, ci.state] } end def link_set @@ -52,11 +52,12 @@ def expanded_links end def states - content_items.group_by(&:locale).each_with_object([]) do |(locale, content_items), states| + grouped_editions = editions.group_by { |e| e.document.locale } + grouped_editions.each_with_object([]) do |(locale, editions), states| states << [locale, ''] - states << [{ v: content_items.first.id.to_s, f: content_items.first.state }, locale] - content_items[1..-1].each_with_index do |content_item, index| - states << [{ v: content_item.id.to_s, f: content_item.state }, content_items[index].try(:id).to_s] + states << [{ v: editions.first.id.to_s, f: editions.first.state }, locale] + editions[1..-1].each_with_index do |edition, index| + states << [{ v: edition.id.to_s, f: edition.state }, editions[index].try(:id).to_s] end end end @@ -69,7 +70,7 @@ def event_timeline def web_content_item @web_content_item ||= ::Queries::GetWebContentItems.find( - latest_content_items.last.id + latest_editions.last.id ) end diff --git a/app/presenters/downstream_presenter.rb b/app/presenters/downstream_presenter.rb index 04ae8faa4..b7ea88571 100644 --- a/app/presenters/downstream_presenter.rb +++ b/app/presenters/downstream_presenter.rb @@ -6,7 +6,7 @@ class DownstreamPresenter def self.present(web_content_item, state_fallback_order:) return {} unless web_content_item - if web_content_item.is_a?(ContentItem) + if web_content_item.is_a?(Edition) # TODO: Add deprecation notice here once we start to migrate other parts of # the app to use WebContentItem. Adding a notice now would be too noisy web_content_item = ::Queries::GetWebContentItems.(web_content_item.id).first @@ -87,7 +87,7 @@ def access_limit end def locale_fallback_order - [web_content_item.locale, ContentItem::DEFAULT_LOCALE].uniq + [web_content_item.locale, Edition::DEFAULT_LOCALE].uniq end def rendered_details diff --git a/app/presenters/queries/available_translations.rb b/app/presenters/queries/available_translations.rb index e412839ea..fc324674b 100644 --- a/app/presenters/queries/available_translations.rb +++ b/app/presenters/queries/available_translations.rb @@ -19,8 +19,9 @@ def translations attr_reader :content_id, :state_fallback_order, :expanded_translations def grouped_translations - ContentItem.where(content_id: content_id, state: state_fallback_order) - .pluck(:id, :locale, :state) + Edition.with_document + .where('documents.content_id': content_id, state: state_fallback_order) + .pluck(:id, 'documents.locale', :state) .sort_by { |(_, _, state)| state_fallback_order.index(state.to_sym) } .group_by { |(_, locale)| locale } end diff --git a/app/presenters/queries/content_item_presenter.rb b/app/presenters/queries/content_item_presenter.rb index 01bdc72c0..ab79821d3 100644 --- a/app/presenters/queries/content_item_presenter.rb +++ b/app/presenters/queries/content_item_presenter.rb @@ -7,10 +7,9 @@ class ContentItemPresenter :include_warnings DEFAULT_FIELDS = ([ - *ContentItem::TOP_LEVEL_FIELDS, + *Edition::TOP_LEVEL_FIELDS, :publication_state, - :user_facing_version, - :base_path, + :content_id, :locale, :lock_version, :updated_at, @@ -22,8 +21,8 @@ def self.present_many(scope, params = {}) new(scope, params).present_many end - def self.present(content_item, params = {}) - scope = ContentItem.where(id: content_item.id) + def self.present(edition, params = {}) + scope = Edition.where(id: edition.id) present_many(scope, params).first end @@ -61,13 +60,11 @@ def full_scope end def latest - ::Queries::GetLatest.call(self.scope) + ::Queries::GetLatest.call(self.scope.joins(:document)) end def join_supporting_objects(scope) - scope = ChangeNote.join_content_items(scope) - - LockVersion.join_content_items(scope) + ChangeNote.join_editions(scope) end def order_and_paginate @@ -86,7 +83,7 @@ def select_fields(scope) when :user_facing_version "content_items.user_facing_version AS user_facing_version" when :lock_version - "lock_versions.number AS lock_version" + "documents.stale_lock_version AS lock_version" when :description "description->>'value' AS description" when :last_edited_at @@ -102,7 +99,9 @@ def select_fields(scope) when :base_path "content_items.base_path as base_path" when :locale - "content_items.locale as locale" + "documents.locale as locale" + when :content_id + "documents.content_id as content_id" when :total "COUNT(*) OVER () as total" else @@ -122,8 +121,8 @@ def search(scope) ( SELECT json_agg((user_facing_version, state)) FROM content_items c - WHERE c.content_id = content_items.content_id - GROUP BY content_id + WHERE c.document_id = documents.id + GROUP BY documents.content_id ) SQL diff --git a/app/presenters/queries/content_item_warnings.rb b/app/presenters/queries/content_item_warnings.rb index 716afb2b2..0e20eb3d8 100644 --- a/app/presenters/queries/content_item_warnings.rb +++ b/app/presenters/queries/content_item_warnings.rb @@ -4,7 +4,7 @@ module ContentItemWarnings def self.call(content_id, state, base_path, document_type) return unless state == "draft" - blocking_content_item_id = ::Queries::CheckForContentItemPreventingDraftFromBeingPublished.call( + blocking_content_item_id = ::Queries::LiveEditionBlockingDraftEdition.call( content_id, base_path, document_type, diff --git a/app/presenters/queries/expanded_link_set.rb b/app/presenters/queries/expanded_link_set.rb index 1df737e37..fdd1eb128 100644 --- a/app/presenters/queries/expanded_link_set.rb +++ b/app/presenters/queries/expanded_link_set.rb @@ -3,7 +3,7 @@ module Queries class ExpandedLinkSet attr_reader :state_fallback_order - def initialize(content_id:, state_fallback_order:, locale_fallback_order: ContentItem::DEFAULT_LOCALE) + def initialize(content_id:, state_fallback_order:, locale_fallback_order: Edition::DEFAULT_LOCALE) @content_id = content_id @state_fallback_order = Array(state_fallback_order.freeze) @locale_fallback_order = Array(locale_fallback_order.freeze) @@ -16,7 +16,7 @@ def links def web_content_items(target_content_ids) return [] unless target_content_ids.present? ::Queries::GetWebContentItems.( - ::Queries::GetContentItemIdsWithFallbacks.( + ::Queries::GetEditionIdsWithFallbacks.( target_content_ids, locale_fallback_order: locale_fallback_order, state_fallback_order: state_fallback_order + [:withdrawn] diff --git a/app/presenters/queries/link_set_presenter.rb b/app/presenters/queries/link_set_presenter.rb index d7e2fdb11..d2fe08c32 100644 --- a/app/presenters/queries/link_set_presenter.rb +++ b/app/presenters/queries/link_set_presenter.rb @@ -2,26 +2,19 @@ module Presenters module Queries class LinkSetPresenter def self.present(link_set) - lock_version = LockVersion.find_by(target: link_set) - new(link_set, lock_version).present + new(link_set).present end - def initialize(link_set, lock_version = nil) + def initialize(link_set) self.link_set = link_set - self.lock_version = lock_version end def present - base = { + { content_id: link_set.content_id, links: links, + version: link_set.stale_lock_version, } - - if lock_version - base.merge(version: lock_version.number) - else - base - end end def links diff --git a/app/queries/base_path_for_state.rb b/app/queries/base_path_for_state.rb index 79c23d912..e1203bab1 100644 --- a/app/queries/base_path_for_state.rb +++ b/app/queries/base_path_for_state.rb @@ -2,37 +2,39 @@ module Queries module BasePathForState extend ArelHelpers - def self.conflict(content_item_id, state, base_path) + def self.conflict(edition_id, state, base_path) return if state == "superseded" - return if state == "unpublished" && Unpublishing.is_substitute?(content_item_id) + return if state == "unpublished" && Unpublishing.is_substitute?(edition_id) - content_items_table = ContentItem.arel_table + documents_table = Document.arel_table + editions_table = Edition.arel_table unpublishings_table = Unpublishing.arel_table allowed_states = state == "draft" ? %w(draft) : %w(published unpublished) - scope = content_items_table + scope = editions_table .project( - content_items_table[:id], - content_items_table[:content_id], - content_items_table[:locale] + editions_table[:id], + documents_table[:content_id], + documents_table[:locale], ) - .where(content_items_table[:id].not_eq(content_item_id)) - .where(content_items_table[:state].in(allowed_states)) - .where(content_items_table[:base_path].eq(base_path)) + .where(editions_table[:id].not_eq(edition_id)) + .where(editions_table[:state].in(allowed_states)) + .where(editions_table[:base_path].eq(base_path)) + .join(documents_table).on(documents_table[:id].eq(editions_table[:document_id])) if %w(published unpublished).include?(state) - unpublished_state = content_items_table[:state].eq("unpublished") - content_items_join = content_items_table[:id].eq(unpublishings_table[:content_item_id]) + unpublished_state = editions_table[:state].eq("unpublished") + editions_join = editions_table[:id].eq(unpublishings_table[:content_item_id]) nil_unpublishing = unpublishings_table[:type].eq(nil) non_substitute = unpublishings_table[:type].not_eq("substitute") scope = scope.outer_join(unpublishings_table) - .on(unpublished_state.and(content_items_join)) + .on(unpublished_state.and(editions_join)) .where(nil_unpublishing.or(non_substitute)) end - scope = scope.order(content_items_table[:created_at].desc).take(1) + scope = scope.order(editions_table[:created_at].desc).take(1) get_rows(scope).first.try(:symbolize_keys) end end diff --git a/app/queries/check_for_content_item_preventing_draft_from_being_published.rb b/app/queries/check_for_content_item_preventing_draft_from_being_published.rb deleted file mode 100644 index a71790a5b..000000000 --- a/app/queries/check_for_content_item_preventing_draft_from_being_published.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Queries - module CheckForContentItemPreventingDraftFromBeingPublished - extend ArelHelpers - - # Checks for any content item which would prevent a content item with the - # specified content_id and base_path from being published (from the draft - # state). - def self.call(content_id, base_path, document_type) - return unless base_path - - if SubstitutionHelper::SUBSTITUTABLE_DOCUMENT_TYPES.include?(document_type) - return # The SubstitionHelper will unpublish any item that is in the way - end - - content_items_table = ContentItem.arel_table - unpublishings_table = Unpublishing.arel_table - - scope = content_items_table - .project( - content_items_table[:id] - ) - .outer_join(unpublishings_table).on( # LEFT OUTER JOIN - content_items_table[:id].eq(unpublishings_table[:content_item_id]) - ) - .where(content_items_table[:content_id].not_eq(content_id)) - .where(content_items_table[:state].in(%w(published unpublished))) - .where(content_items_table[:document_type].not_in( - SubstitutionHelper::SUBSTITUTABLE_DOCUMENT_TYPES - )) - .where(content_items_table[:base_path].eq(base_path)) - .where( - unpublishings_table[:type].not_eq("substitute") - .or(unpublishings_table[:type].eq(nil)) - ) - - rows = get_rows(scope) - - if rows.length > 1 - raise "Multiple rows returned in CheckForContentItemPreventingDraftFromBeingPublished" - end - - rows.first["id"].to_i if rows.first - end - end -end diff --git a/app/queries/content_dependencies.rb b/app/queries/content_dependencies.rb index 04dffe134..ea988c2ae 100644 --- a/app/queries/content_dependencies.rb +++ b/app/queries/content_dependencies.rb @@ -1,12 +1,11 @@ module Queries - # This class resolves the depencies for a given subject content item with - # a provided content_id and locale + # This class resolves the depencies for a given content_id and locale # # There are 3 types of dependency this resolves: - # 1 - Content items that are linked to the subject of dependency resolution + # 1 - Documents that are linked to the subject of dependency resolution # (eg for a subject of a if b has a link to a b will be returned), # for certain link types these are recursed forming a tree structure. - # 2 - Content Items which have an automoatic reverse link to the subject. + # 2 - Documents which have an automoatic reverse link to the subject. # These are items this subject links to and is represented reciprocally # in the item linked to. eg if our subject (A) has a parent of B, B would # automatically have a link to A of type children. @@ -14,22 +13,22 @@ module Queries # added as dependencies, as well as all translations of the content_ids # found. class ContentDependencies - def initialize(content_id:, locale:, state_fallback_order:) + def initialize(content_id:, locale:, content_stores:) @content_id = content_id @locale = locale - @state_fallback_order = state_fallback_order + @content_stores = content_stores end def call content_ids = linked_to(content_id) + automatic_reverse_links(content_id) + [content_id] - with_locales = Queries::LocalesForContentItems.call(content_ids.uniq, state_fallback_order) + with_locales = Queries::LocalesForEditions.call(content_ids.uniq, content_stores) calling_item = locale ? [content_id, locale] : nil with_locales - [calling_item] end private - attr_reader :content_id, :locale, :state_fallback_order + attr_reader :content_id, :locale, :content_stores def linked_to(content_id) Queries::LinkedTo.new(content_id, DependeeExpansionRules).call diff --git a/app/queries/get_change_history.rb b/app/queries/get_change_history.rb index d95443f49..9aa57235c 100644 --- a/app/queries/get_change_history.rb +++ b/app/queries/get_change_history.rb @@ -2,17 +2,17 @@ module Queries class GetChangeHistory def self.call(publishing_app) Queries::GetLatest. - call(content_items(publishing_app)). + call(editions(publishing_app)). pluck(:id, "details->>'change_history'"). - flat_map do |content_item_id, item_history| + flat_map do |editions_id, item_history| JSON.parse(item_history).map do |item_history_element| - item_history_element.symbolize_keys.merge(content_item_id: content_item_id) + item_history_element.symbolize_keys.merge(content_item_id: editions_id) end end end - def self.content_items(publishing_app) - ContentItem. + def self.editions(publishing_app) + Edition. where(publishing_app: publishing_app). where("json_array_length(details->'change_history') > 0") end diff --git a/app/queries/get_content.rb b/app/queries/get_content.rb index dc16a4c7e..459ba5e01 100644 --- a/app/queries/get_content.rb +++ b/app/queries/get_content.rb @@ -1,13 +1,14 @@ module Queries module GetContent def self.call(content_id, locale = nil, version: nil, include_warnings: false) - locale_to_use = locale || ContentItem::DEFAULT_LOCALE + locale_to_use = locale || Edition::DEFAULT_LOCALE - content_items = ContentItem.where(content_id: content_id, locale: locale_to_use) - content_items = content_items.where(user_facing_version: version) if version + editions = Edition.with_document + .where(documents: { content_id: content_id, locale: locale_to_use }) + editions = editions.where(user_facing_version: version) if version response = Presenters::Queries::ContentItemPresenter.present_many( - content_items, + editions, include_warnings: include_warnings ).first @@ -31,14 +32,14 @@ def self.raise_not_found(message) end def self.not_found_message(content_id, locale, version) - if (locale || version) && ContentItem.exists?(content_id: content_id) + if (locale || version) && Document.exists?(content_id: content_id) locale_message = locale ? "locale: #{locale}" : nil version_message = version ? "version: #{version}" : nil reason = [locale_message, version_message].compact.join(" and ") - "Could not find #{reason} for content item with content_id: #{content_id}" + "Could not find #{reason} for document with content_id: #{content_id}" else - "Could not find content item with content_id: #{content_id}" + "Could not find document with content_id: #{content_id}" end end private_class_method :raise_not_found, :not_found_message diff --git a/app/queries/get_content_collection.rb b/app/queries/get_content_collection.rb index 47a3bdcae..fc11b2ef9 100644 --- a/app/queries/get_content_collection.rb +++ b/app/queries/get_content_collection.rb @@ -45,12 +45,12 @@ def total :states, ) - def content_items - scope = ContentItem.where(document_type: lookup_document_types) + def editions + scope = Edition.where(document_type: lookup_document_types) scope = scope.where(publishing_app: publishing_app) if publishing_app scope = scope.where(state: states) if states.present? - scope = scope.where(locale: locale) unless locale == "all" - scope = Link.filter_content_items(scope, link_filters) unless link_filters.blank? + scope = scope.with_document.where("documents.locale": locale) unless locale == "all" + scope = Link.filter_editions(scope, link_filters) unless link_filters.blank? scope end @@ -82,7 +82,7 @@ def default_fields def query @query ||= presenter.new( - content_items, + editions, fields: fields, order: pagination.order, offset: pagination.offset, diff --git a/app/queries/get_content_item_ids_with_fallbacks.rb b/app/queries/get_content_item_ids_with_fallbacks.rb deleted file mode 100644 index e7583e0de..000000000 --- a/app/queries/get_content_item_ids_with_fallbacks.rb +++ /dev/null @@ -1,62 +0,0 @@ -module Queries - class GetContentItemIdsWithFallbacks - extend ArelHelpers - - def self.call(content_ids, locale_fallback_order: ContentItem::DEFAULT_LOCALE, state_fallback_order:) - state_fallback_order = Array(state_fallback_order).map(&:to_s) - locale_fallback_order = Array(locale_fallback_order).map(&:to_s) - - content_items = ContentItem.arel_table - unpublishings = Unpublishing.arel_table - - fallback_scope = content_items.project( - content_items[:id], - content_items[:content_id], - ) - .where(content_items[:content_id].in(content_ids)) - .where(content_items[:document_type].not_in(::ContentItem::NON_RENDERABLE_FORMATS)) - .where(content_items[:locale].in(locale_fallback_order)) - - if state_fallback_order.include?("withdrawn") - fallback_scope = fallback_scope.where(content_items[:state].in(state_fallback_order).or(content_items[:state] - .eq("unpublished") - .and(unpublishings[:type] - .eq("withdrawal")))) - .join(unpublishings, Arel::Nodes::OuterJoin).on(unpublishings[:content_item_id].eq(content_items[:id])) - else - fallback_scope = fallback_scope.where(content_items[:state].in(state_fallback_order)) - end - - fallback_scope = fallback_scope.order( - order_by_clause(:content_items, :state, state_fallback_order), - order_by_clause(:content_items, :locale, locale_fallback_order) - ) - - fallbacks = cte(fallback_scope, as: "fallbacks") - - aggregates = cte( - fallbacks.table - .project(Arel::Nodes::NamedFunction.new("array_agg", [fallbacks.table[:id]], "ids")) - .group(fallbacks.table[:content_id]) - .with(fallbacks.compiled_scope), - as: "aggregates" - ) - - get_column( - aggregates.table - .project(Arel::Nodes::SqlLiteral.new('aggregates.ids[1] AS id')) - .with(aggregates.compiled_scope) - .to_sql - ) - end - - # Arel::Nodes::Case is coming in arel master - def self.order_by_clause(table, attribute, values) - sql = %{CASE "#{table}"."#{attribute}" } - sql << values.map.with_index { |v, i| "WHEN '#{v}' THEN #{i}" }.join(" ") - sql << " ELSE #{values.size} END" - Arel::Nodes::SqlLiteral.new(sql) - end - private_class_method :order_by_clause - end -end diff --git a/app/queries/get_edition_ids_with_fallbacks.rb b/app/queries/get_edition_ids_with_fallbacks.rb new file mode 100644 index 000000000..c43b3d2eb --- /dev/null +++ b/app/queries/get_edition_ids_with_fallbacks.rb @@ -0,0 +1,57 @@ +module Queries + class GetEditionIdsWithFallbacks + def self.call(content_ids, state_fallback_order:, locale_fallback_order: Edition::DEFAULT_LOCALE) + state_fallback_order = Array.wrap(state_fallback_order).map(&:to_s) + locale_fallback_order = Array.wrap(locale_fallback_order).map(&:to_s) + + Edition.with_document.left_outer_joins(:unpublishing) + .where(documents: { content_id: content_ids }) + .where(where_state(state_fallback_order)) + .where(documents: { locale: locale_fallback_order }) + .where.not(document_type: Edition::NON_RENDERABLE_FORMATS) + .order("documents.content_id ASC") + .order(order_by_clause("content_items", "state", state_ordering(state_fallback_order))) + .order(order_by_clause("documents", "locale", locale_fallback_order)) + .pluck("DISTINCT ON (documents.content_id) documents.content_id, content_items.id") + .map(&:last) + end + + def self.where_state(state_fallback_order) + without_withdrawn = state_fallback_order - ["withdrawn"] + if without_withdrawn.present? + state_check = Edition.arel_table[:state].in(without_withdrawn) + else + state_check = nil + end + + if state_fallback_order.include?("withdrawn") + withdrawn_check = Edition.arel_table[:state].eq("unpublished") + .and(Unpublishing.arel_table[:type].eq("withdrawal")) + else + withdrawn_check = nil + end + + if state_check && withdrawn_check + state_check.or(withdrawn_check) + else + state_check || withdrawn_check + end + end + private_class_method :where_state + + def self.order_by_clause(table, attribute, values) + sql = %{CASE "#{table}"."#{attribute}" } + sql << values.map.with_index { |v, i| "WHEN '#{v}' THEN #{i}" }.join(" ") + sql << " ELSE #{values.size} END" + end + private_class_method :order_by_clause + + # We have a special case where a state of withdrawn can be passed in, + # this is not actually a state but an unpublishing type. So when this is + # passed in it is changed to "unpublished" for ordering purposes. + def self.state_ordering(state_fallback_order) + state_fallback_order.map { |state| state == "withdrawn" ? "unpublished" : state } + end + private_class_method :state_ordering + end +end diff --git a/app/queries/get_expanded_links.rb b/app/queries/get_expanded_links.rb index bde634a31..f4014dc50 100644 --- a/app/queries/get_expanded_links.rb +++ b/app/queries/get_expanded_links.rb @@ -2,17 +2,16 @@ module Queries class GetExpandedLinks def self.call(content_id, locale) link_set = find_link_set(content_id) - lock_version = LockVersion.find_by(target: link_set) expanded_link_set = Presenters::Queries::ExpandedLinkSet.new( content_id: content_id, state_fallback_order: [:draft, :published], - locale_fallback_order: [locale, ContentItem::DEFAULT_LOCALE].compact + locale_fallback_order: [locale, Edition::DEFAULT_LOCALE].compact ) { content_id: content_id, expanded_links: expanded_link_set.links, - version: lock_version ? lock_version.number : 0 + version: link_set.stale_lock_version, } end diff --git a/app/queries/get_latest.rb b/app/queries/get_latest.rb index 6866287cf..d185a25fd 100644 --- a/app/queries/get_latest.rb +++ b/app/queries/get_latest.rb @@ -1,23 +1,15 @@ module Queries module GetLatest class << self - # Returns a new scope for the content items with the highest user-facing - # version number per content_id and locale of the given scope. - def call(content_item_scope) - scope = content_item_scope.select(:id, :content_id, 'content_items.locale', :user_facing_version) + def call(edition_scope) + edition_scope.where(id: inner_scope(edition_scope)) + end - ContentItem.joins <<-SQL - INNER JOIN ( - WITH scope AS (#{scope.to_sql}) - SELECT s1.id FROM scope s1 - LEFT OUTER JOIN scope s2 ON - s1.content_id = s2.content_id AND - s1.locale = s2.locale AND - s1.user_facing_version < s2.user_facing_version - WHERE s2.content_id IS NULL - ) AS latest_versions - ON latest_versions.id = content_items.id - SQL + def inner_scope(edition_scope) + edition_scope + .reorder(:document_id, ["user_facing_version DESC"] + edition_scope.order_values) + .select("distinct on(content_items.document_id) content_items.document_id, content_items.id") + .map(&:id) end end end diff --git a/app/queries/get_linkables.rb b/app/queries/get_linkables.rb index e1f18b76b..f5d30a73f 100644 --- a/app/queries/get_linkables.rb +++ b/app/queries/get_linkables.rb @@ -18,7 +18,7 @@ def initialize(document_type:) end def call - latest_updated_at = ContentItem + latest_updated_at = Edition .where(document_type: [document_type, "placeholder_#{document_type}"]) .order('updated_at DESC') .limit(1) @@ -27,10 +27,10 @@ def call Rails.cache.fetch ["linkables", document_type, latest_updated_at] do Queries::GetWebContentItems.( - Queries::GetContentItemIdsWithFallbacks.( - ContentItem.distinct.where( + Queries::GetEditionIdsWithFallbacks.( + Edition.with_document.distinct.where( document_type: [document_type, "placeholder_#{document_type}"] - ).pluck(:content_id), + ).pluck('documents.content_id'), state_fallback_order: [:published, :draft] ), LinkablePresenter diff --git a/app/queries/get_linked.rb b/app/queries/get_linked.rb index 12c9d1e8e..cfb9e2ef7 100644 --- a/app/queries/get_linked.rb +++ b/app/queries/get_linked.rb @@ -18,9 +18,9 @@ def call .joins(:link_set) .pluck(:content_id) - content_items = ContentItem.where(content_id: content_ids) + editions = Edition.with_document.where("documents.content_id": content_ids) - presented = presenter.present_many(content_items, fields: fields) + presented = presenter.present_many(editions, fields: fields) presented.map { |p| filter_fields(p).as_json } end @@ -29,8 +29,8 @@ def call attr_accessor :target_content_id, :link_type, :fields def validate_presence_of_item! - return if ContentItem.exists?( - content_id: target_content_id, + return if Edition.joins(:document).exists?( + documents: { content_id: target_content_id }, state: %w(draft published), ) @@ -67,7 +67,7 @@ def filter_fields(hash) end def permitted_fields - ContentItem.column_names + %w(base_path locale publication_state) + Edition.column_names + %w(content_id base_path locale publication_state) end def presenter diff --git a/app/queries/get_web_content_items.rb b/app/queries/get_web_content_items.rb index 8719add7d..5a733c887 100644 --- a/app/queries/get_web_content_items.rb +++ b/app/queries/get_web_content_items.rb @@ -2,29 +2,29 @@ module Queries class GetWebContentItems extend ArelHelpers - def self.call(content_item_ids, presenter = WebContentItem) - content_items = ContentItem.arel_table - filtered = scope - .where(content_items[:id].in(content_item_ids)) + def self.call(edition_ids, presenter = WebContentItem) + editions = Edition.arel_table + filtered = scope.where(editions[:id].in(edition_ids)) get_rows(filtered).map do |row| presenter.from_hash(row) end end - def self.find(content_item_id) - call(content_item_id).first + def self.find(edition_id) + call(edition_id).first end def self.for_content_store(content_id, locale, include_draft = false) - content_items = ContentItem.arel_table + documents = Document.arel_table + editions = Edition.arel_table unpublishings = Unpublishing.arel_table allowed_states = [:published, :unpublished] allowed_states << :draft if include_draft - filtered = scope(content_items[:user_facing_version].desc) - .where(content_items[:content_id].eq(content_id)) - .where(content_items[:locale].eq(locale)) - .where(content_items[:state].in(allowed_states)) + filtered = scope(editions[:user_facing_version].desc) + .where(documents[:content_id].eq(content_id)) + .where(documents[:locale].eq(locale)) + .where(editions[:state].in(allowed_states)) .where( unpublishings[:type].eq(nil).or( unpublishings[:type].not_eq("substitute") @@ -38,40 +38,42 @@ def self.for_content_store(content_id, locale, include_draft = false) end def self.scope(order = nil) - content_items = ContentItem.arel_table + documents = Document.arel_table + editions = Edition.arel_table unpublishings = Unpublishing.arel_table - content_items + editions .project( - content_items[:id], - content_items[:analytics_identifier], - content_items[:content_id], - content_items[:description], - content_items[:details], - content_items[:document_type], - content_items[:first_published_at], - content_items[:last_edited_at], - content_items[:need_ids], - content_items[:phase], - content_items[:public_updated_at], - content_items[:publishing_app], - content_items[:redirects], - content_items[:rendering_app], - content_items[:routes], - content_items[:schema_name], - content_items[:title], - content_items[:update_type], - content_items[:base_path], - content_items[:state], - content_items[:locale], - content_items[:user_facing_version], + editions[:id], + editions[:analytics_identifier], + documents[:content_id], + editions[:description], + editions[:details], + editions[:document_type], + editions[:first_published_at], + editions[:last_edited_at], + editions[:need_ids], + editions[:phase], + editions[:public_updated_at], + editions[:publishing_app], + editions[:redirects], + editions[:rendering_app], + editions[:routes], + editions[:schema_name], + editions[:title], + editions[:update_type], + editions[:base_path], + editions[:state], + documents[:locale], + editions[:user_facing_version], unpublishings[:type].as("unpublishing_type") ) + .join(documents).on(editions[:document_id].eq(documents[:id])) .outer_join(unpublishings).on( - content_items[:id].eq(unpublishings[:content_item_id]) - .and(content_items[:state].eq("unpublished")) + editions[:id].eq(unpublishings[:content_item_id]) + .and(editions[:state].eq("unpublished")) ) - .order(order || content_items[:id].asc) + .order(order || editions[:id].asc) end end end diff --git a/app/queries/live_edition_blocking_draft_edition.rb b/app/queries/live_edition_blocking_draft_edition.rb new file mode 100644 index 000000000..8660c1900 --- /dev/null +++ b/app/queries/live_edition_blocking_draft_edition.rb @@ -0,0 +1,23 @@ +module Queries + module LiveEditionBlockingDraftEdition + # Checks for a live edition which would prevent a draft edition with the + # specified content_id and base_path from being published (from the draft + # state). + def self.call(content_id, base_path, document_type) + return unless base_path + + if SubstitutionHelper::SUBSTITUTABLE_DOCUMENT_TYPES.include?(document_type) + return # The SubstitionHelper will unpublish any item that is in the way + end + + conflicts = Edition.with_document + .where(base_path: base_path, content_store: :live) + .where.not( + "documents.content_id": content_id, + document_type: SubstitutionHelper::SUBSTITUTABLE_DOCUMENT_TYPES, + ).pluck(:id) + + conflicts.first unless conflicts.empty? + end + end +end diff --git a/app/queries/locales_for_content_items.rb b/app/queries/locales_for_content_items.rb deleted file mode 100644 index 0d8009826..000000000 --- a/app/queries/locales_for_content_items.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Queries - module LocalesForContentItems - extend ArelHelpers - - # returns an array of form: - # [ - # [content_id, locale], - # [content_id, locale], - # ] - def self.call( - content_ids, - states = %w[draft published unpublished], - include_substitutes = false - ) - content_items_table = ContentItem.arel_table - - scope = content_items_table - .project( - content_items_table[:content_id], - content_items_table[:locale] - ) - .distinct - .where(content_items_table[:content_id].in(content_ids)) - .where(content_items_table[:state].in(states)) - .order(content_items_table[:content_id].asc, content_items_table[:locale].asc) - - unless include_substitutes - unpublishings_table = Unpublishing.arel_table - - scope = scope.outer_join(unpublishings_table).on( - content_items_table[:id].eq(unpublishings_table[:content_item_id]) - .and(content_items_table[:state].eq("unpublished")) - ) - .where( - unpublishings_table[:type].eq(nil).or( - unpublishings_table[:type].not_eq("substitute") - ) - ) - end - - get_rows(scope).map { |row| [row["content_id"], row["locale"]] } - end - end -end diff --git a/app/queries/locales_for_editions.rb b/app/queries/locales_for_editions.rb new file mode 100644 index 000000000..ed46ccdfc --- /dev/null +++ b/app/queries/locales_for_editions.rb @@ -0,0 +1,22 @@ +module Queries + module LocalesForEditions + # returns an array of form: + # [ + # [content_id, locale], + # [content_id, locale], + # ] + def self.call( + content_ids, + content_stores = %w[draft live] + ) + Document.joins(:editions) + .where( + content_id: content_ids, + content_items: { content_store: content_stores } + ) + .distinct + .order(:content_id, :locale) + .pluck(:content_id, :locale) + end + end +end diff --git a/app/services/downstream_service.rb b/app/services/downstream_service.rb index f05920b86..c84094455 100644 --- a/app/services/downstream_service.rb +++ b/app/services/downstream_service.rb @@ -53,12 +53,12 @@ def self.discard_from_draft_content_store(base_path) def self.draft_at_base_path?(base_path) return false unless base_path - ContentItem.exists?(base_path: base_path, state: "draft") + Edition.exists?(base_path: base_path, state: "draft") end def self.discard_draft_base_path_conflict?(base_path) return false unless base_path - ContentItem.exists?( + Edition.exists?( base_path: base_path, state: %w(draft published unpublished), ) diff --git a/app/substitution_helper.rb b/app/substitution_helper.rb index d835a191f..0687d0e3e 100644 --- a/app/substitution_helper.rb +++ b/app/substitution_helper.rb @@ -20,17 +20,18 @@ def clear!( ) raise NilBasePathError if base_path.nil? - blocking_items = ContentItem.where(base_path: base_path, locale: locale, state: state) + blocking_items = Edition.with_document + .where(base_path: base_path, state: state, "documents.locale": locale) blocking_items.each do |blocking_item| - mismatch = (blocking_item.content_id != new_item_content_id) + mismatch = (blocking_item.document.content_id != new_item_content_id) allowed_to_substitute = (substitute?(new_item_document_type) || substitute?(blocking_item.document_type)) if mismatch && allowed_to_substitute if state == "draft" Commands::V2::DiscardDraft.call({ - content_id: blocking_item.content_id, - locale: locale, + content_id: blocking_item.document.content_id, + locale: blocking_item.document.locale, }, downstream: downstream, nested: nested, diff --git a/app/validators/state_for_document_validator.rb b/app/validators/state_for_document_validator.rb new file mode 100644 index 000000000..d8869ddb4 --- /dev/null +++ b/app/validators/state_for_document_validator.rb @@ -0,0 +1,18 @@ +class StateForDocumentValidator < ActiveModel::Validator + def validate(record) + return unless record.state && record.document && %w(draft published unpublished).include?(record.state) + + criteria = { + document: record.document, + state: record.state == "draft" ? "draft" : %w(published unpublished), + } + + conflict = Edition.where(criteria).where.not(id: record.id).order(nil).first + + if conflict + error = "state=#{record.state} and document=#{record.document_id} "\ + "conflicts with edition id=#{conflict[:id]}" + record.errors.add(:base, error) + end + end +end diff --git a/app/validators/state_for_locale_validator.rb b/app/validators/state_for_locale_validator.rb deleted file mode 100644 index 1f9227621..000000000 --- a/app/validators/state_for_locale_validator.rb +++ /dev/null @@ -1,20 +0,0 @@ -class StateForLocaleValidator < ActiveModel::Validator - def validate(record) - return unless record.state && record.locale && %w(draft published unpublished).include?(record.state) - - criteria = { - content_id: record.content_id, - state: record.state == "draft" ? "draft" : %w(published unpublished), - locale: record.locale, - } - - conflict = ContentItem.where(criteria).where.not(id: record.id).order(nil).first - - if conflict - error = "state=#{record.state} and locale=#{record.locale} " - error << "for content item=#{record.content_id} conflicts with " - error << "content item id=#{conflict[:id]}" - record.errors.add(:base, error) - end - end -end diff --git a/app/validators/unpublishing_redirect_validator.rb b/app/validators/unpublishing_redirect_validator.rb index 2871cf6ec..b3896f4be 100644 --- a/app/validators/unpublishing_redirect_validator.rb +++ b/app/validators/unpublishing_redirect_validator.rb @@ -1,8 +1,8 @@ class UnpublishingRedirectValidator < ActiveModel::Validator def validate(unpublishing) - return unless unpublishing.content_item + return unless unpublishing.edition - base_path = unpublishing.content_item.base_path + base_path = unpublishing.edition.base_path if base_path == unpublishing.alternative_path unpublishing.errors.add( diff --git a/app/validators/version_for_document_validator.rb b/app/validators/version_for_document_validator.rb new file mode 100644 index 000000000..347a7a256 --- /dev/null +++ b/app/validators/version_for_document_validator.rb @@ -0,0 +1,19 @@ +class VersionForDocumentValidator < ActiveModel::Validator + def validate(record) + return unless record.document && record.user_facing_version + + criteria = { + document: record.document, + user_facing_version: record.user_facing_version, + } + + conflict = Edition.where(criteria).where.not(id: record.id).order(nil).first + + if conflict + error = "user_facing_version=#{record.user_facing_version} and "\ + "document=#{record.document_id} conflicts with edition "\ + "id=#{conflict[:id]}" + record.errors.add(:base, error) + end + end +end diff --git a/app/validators/version_for_locale_validator.rb b/app/validators/version_for_locale_validator.rb deleted file mode 100644 index 20c596b4e..000000000 --- a/app/validators/version_for_locale_validator.rb +++ /dev/null @@ -1,20 +0,0 @@ -class VersionForLocaleValidator < ActiveModel::Validator - def validate(record) - return unless record.locale && record.user_facing_version - - criteria = { - content_id: record.content_id, - user_facing_version: record.user_facing_version, - locale: record.locale, - } - - conflict = ContentItem.where(criteria).where.not(id: record.id).order(nil).first - - if conflict - error = "user_facing_version=#{record.user_facing_version} and " - error << "locale=#{record.locale} for content item=#{record.content_id} " - error << "conflicts with content item id=#{conflict[:id]}" - record.errors.add(:base, error) - end - end -end diff --git a/app/workers/dependency_resolution_worker.rb b/app/workers/dependency_resolution_worker.rb index 5150b0b2a..a8dc0a2b5 100644 --- a/app/workers/dependency_resolution_worker.rb +++ b/app/workers/dependency_resolution_worker.rb @@ -30,11 +30,10 @@ def assign_attributes(args) end def dependencies - states = draft? ? %w[draft published unpublished] : %w[published unpublished] Queries::ContentDependencies.new( content_id: content_id, locale: locale, - state_fallback_order: states, + content_stores: draft? ? %w[draft live] : %w[live], ).call end diff --git a/app/workers/downstream_draft_worker.rb b/app/workers/downstream_draft_worker.rb index 685ce4bfa..c5a7ccd36 100644 --- a/app/workers/downstream_draft_worker.rb +++ b/app/workers/downstream_draft_worker.rb @@ -27,7 +27,7 @@ def perform(args = {}) assign_attributes(args.symbolize_keys) unless web_content_item - raise AbortWorkerError.new("A downstreamable content item was not found for content_id: #{content_id} and locale: #{locale}") + raise AbortWorkerError.new("A downstreamable edition was not found for content_id: #{content_id} and locale: #{locale}") end unless dependency_resolution_source_content_id.nil? diff --git a/app/workers/downstream_live_worker.rb b/app/workers/downstream_live_worker.rb index 07b61fae9..e56ce1c3d 100644 --- a/app/workers/downstream_live_worker.rb +++ b/app/workers/downstream_live_worker.rb @@ -28,7 +28,7 @@ def perform(args = {}) assign_attributes(args.symbolize_keys) unless web_content_item - raise AbortWorkerError.new("A downstreamable content item was not found for content_id: #{content_id} and locale: #{locale}") + raise AbortWorkerError.new("A downstreamable edition was not found for content_id: #{content_id} and locale: #{locale}") end unless dependency_resolution_source_content_id.nil? diff --git a/bench/content_item_presenter.rb b/bench/content_item_presenter.rb index 572dbad6c..17bb015ad 100644 --- a/bench/content_item_presenter.rb +++ b/bench/content_item_presenter.rb @@ -10,7 +10,7 @@ end def present(number_of_items) - scope = ContentItem.limit(number_of_items).order("id DESC") + scope = Edition.limit(number_of_items).order(id: :desc) $queries = 0 puts "Presenting #{number_of_items} content items" diff --git a/bench/downstream_payload.rb b/bench/downstream_payload.rb index 29e175f60..9ed23b2be 100644 --- a/bench/downstream_payload.rb +++ b/bench/downstream_payload.rb @@ -56,7 +56,7 @@ benchmarks.each do |name, content_id| content_item_ids = Queries::GetLatest.( - ContentItem.where(content_id: content_id, state: :published) + Edition.where(content_id: content_id) ).pluck(:id) web_content_item = Queries::GetWebContentItems.(content_item_ids).first diff --git a/bench/downstream_presenter.rb b/bench/downstream_presenter.rb index b3eedd9ff..bcb4f264c 100644 --- a/bench/downstream_presenter.rb +++ b/bench/downstream_presenter.rb @@ -54,7 +54,7 @@ benchmarks.each do |name, content_id| content_item_ids = Queries::GetLatest.( - ContentItem.where(content_id: content_id, state: :published) + Edition.where(content_id: content_id, state: :published) ).pluck(:id) web_content_item = Queries::GetWebContentItems.(content_item_ids).first diff --git a/bench/get_content_items.rb b/bench/get_content_items.rb index e8341b655..58129fde8 100644 --- a/bench/get_content_items.rb +++ b/bench/get_content_items.rb @@ -10,7 +10,7 @@ queries = 0 ActiveSupport::Notifications.subscribe("sql.active_record") { |_| queries += 1 } -StackProf.run(mode: :wall, out: "tmp/get_content_items_wall.dump") do +StackProf.run(mode: :wall, out: "tmp/get_editions_wall.dump") do puts Benchmark.measure { 10.times do Presenters::ResultsPresenter.new( diff --git a/bench/get_expanded_links.rb b/bench/get_expanded_links.rb index 2f6d47fe3..b88002ddc 100644 --- a/bench/get_expanded_links.rb +++ b/bench/get_expanded_links.rb @@ -49,8 +49,8 @@ def get_content_id_and_locale(content_id) [ - content_id, - ContentItem.where(content_id: content_id, state: :published).pluck(:locale).first, + content_id, + Edition.where(content_id: content_id).pluck(:locale).first, ] end diff --git a/bench/linkables.rb b/bench/linkables.rb index 77e20a8a1..182d98171 100644 --- a/bench/linkables.rb +++ b/bench/linkables.rb @@ -8,7 +8,7 @@ abort "Refusing to run outside of development" unless Rails.env.development? -benchmarks = ContentItem.where(document_type: ['taxon', 'organisation', 'topic', 'mainstream_browse_page', 'policy']).pluck(:document_type).uniq +benchmarks = Edition.where(document_type: ['taxon', 'organisation', 'topic', 'mainstream_browse_page', 'policy']).pluck(:document_type).uniq benchmarks.each do |document_type| queries = 0 diff --git a/bench/lookup_by_base_path.rb b/bench/lookup_by_base_path.rb index e91ce4b65..121ae2adf 100644 --- a/bench/lookup_by_base_path.rb +++ b/bench/lookup_by_base_path.rb @@ -18,7 +18,7 @@ StackProf.run(mode: :wall, out: "tmp/lookup_by_base_path_#{base_path.gsub(/\//, '_').downcase}_wall.dump") do puts Benchmark.measure { 10.times do - ContentItem + Edition .where(state: states, base_path: [base_path]) .pluck(:base_path, :content_id) .uniq diff --git a/bench/patch_link_set.rb b/bench/patch_link_set.rb index 99ceefb38..d211caf33 100644 --- a/bench/patch_link_set.rb +++ b/bench/patch_link_set.rb @@ -14,7 +14,7 @@ $queries += 1 end -content_items = 100.times.map do +editions = 100.times.map do title = Faker::Company.catch_phrase { content_id: SecureRandom.uuid, @@ -31,9 +31,7 @@ redirects: [], publishing_app: "performance-testing", rendering_app: "performance-testing", - details: { - body: "

#{Faker::Lorem.paragraphs(10)}

" - }, + details: {}, phase: 'live', need_ids: [] } @@ -41,7 +39,7 @@ begin puts "Publishing content items..." - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item) Commands::V2::Publish.call(content_id: item[:content_id], update_type: 'major') end @@ -50,8 +48,8 @@ puts "Patching links..." - content_ids = content_items.map { |ci| ci[:content_id] } - payloads = content_items.map do |ci| + content_ids = editions.map { |ci| ci[:content_id] } + payloads = editions.map do |ci| links = content_ids.sample(10).reject { |id| id == ci[:content_id] } {content_id: ci[:content_id], links: {foos: links}} end @@ -69,12 +67,12 @@ puts "#{$queries} SQL queries" ensure - scope = ContentItem.where(publishing_app: 'performance-testing') + scope = Edition.where(publishing_app: 'performance-testing') LinkSet.includes(:links).where(content_id: scope.pluck(:content_id)).destroy_all - Location.where(content_item: scope).delete_all - State.where(content_item: scope).delete_all - Translation.where(content_item: scope).delete_all - UserFacingVersion.where(content_item: scope).delete_all + Location.where(edition: scope).delete_all + State.where(edition: scope).delete_all + Translation.where(edition: scope).delete_all + UserFacingVersion.where(edition: scope).delete_all LockVersion.where(target: scope).delete_all PathReservation.where(publishing_app: 'performance-testing').delete_all scope.delete_all diff --git a/bench/publish.rb b/bench/publish.rb index 65175457e..9ae88dbf8 100644 --- a/bench/publish.rb +++ b/bench/publish.rb @@ -9,9 +9,9 @@ abort "Refusing to run outside of development" unless Rails.env.development? -def publish(content_items) +def publish(editions) puts Benchmark.measure { - content_items.each do |item| + editions.each do |item| Commands::V2::Publish.call(content_id: item[:content_id], update_type: 'major') print "." end @@ -24,7 +24,7 @@ def publish(content_items) $queries += 1 end -content_items = 100.times.map do +editions = 100.times.map do title = Faker::Company.catch_phrase { content_id: SecureRandom.uuid, @@ -41,9 +41,7 @@ def publish(content_items) redirects: [], publishing_app: "performance-testing", rendering_app: "performance-testing", - details: { - body: "

#{Faker::Lorem.paragraphs(10)}

" - }, + details: {}, phase: 'live', need_ids: [] } @@ -51,7 +49,7 @@ def publish(content_items) begin puts "Creating drafts..." - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item) end @@ -59,12 +57,12 @@ def publish(content_items) puts "Publishing..." - StackProf.run(mode: :wall, out: "tmp/publish_wall.dump") { publish(content_items) } + StackProf.run(mode: :wall, out: "tmp/publish_wall.dump") { publish(editions) } puts "#{$queries} SQL queries" puts "Creating new drafts..." - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item.merge(title: Faker::Company.catch_phrase)) end @@ -72,17 +70,17 @@ def publish(content_items) puts "Superseding..." - StackProf.run(mode: :wall, out: "tmp/supersede_wall.dump") { publish(content_items) } + StackProf.run(mode: :wall, out: "tmp/supersede_wall.dump") { publish(editions) } puts "#{$queries} SQL queries" ensure - scope = ContentItem.where(publishing_app: 'performance-testing') + scope = Edition.where(publishing_app: 'performance-testing') LinkSet.includes(:links).where(content_id: scope.pluck(:content_id)).destroy_all - Location.where(content_item: scope).delete_all - State.where(content_item: scope).delete_all - Translation.where(content_item: scope).delete_all - UserFacingVersion.where(content_item: scope).delete_all + Location.where(edition: scope).delete_all + State.where(edition: scope).delete_all + Translation.where(edition: scope).delete_all + UserFacingVersion.where(edition: scope).delete_all LockVersion.where(target: scope).delete_all PathReservation.where(publishing_app: 'performance-testing').delete_all scope.delete_all diff --git a/bench/put_content.rb b/bench/put_content.rb index a7a705413..06cfefb7d 100644 --- a/bench/put_content.rb +++ b/bench/put_content.rb @@ -20,7 +20,7 @@ content_id = SecureRandom.uuid title = Faker::Company.catch_phrase -content_items = 100.times.map do +editions = 100.times.map do title = Faker::Company.catch_phrase if new_item content_id = SecureRandom.uuid if new_item { @@ -38,9 +38,7 @@ redirects: [], publishing_app: "performance-testing", rendering_app: "performance-testing", - details: { - body: "

#{Faker::Lorem.paragraphs(10)}

" - }, + details: {}, phase: 'live', need_ids: [] } @@ -49,7 +47,7 @@ begin if redraft puts "Creating published items..." - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item) Commands::V2::Publish.call(content_id: item[:content_id], update_type: 'major') end @@ -58,7 +56,7 @@ puts "Publishing..." StackProf.run(mode: :wall, out: "tmp/put_content_wall.dump") do puts Benchmark.measure { - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item.merge(title: Faker::Company.catch_phrase)) print "." end @@ -68,7 +66,7 @@ else StackProf.run(mode: :wall, out: "tmp/put_content_wall.dump") do puts Benchmark.measure { - content_items.each do |item| + editions.each do |item| Commands::V2::PutContent.call(item) print "." end @@ -80,12 +78,12 @@ puts "#{$queries} SQL queries" ensure - scope = ContentItem.where(publishing_app: 'performance-testing') + scope = Edition.where(publishing_app: 'performance-testing') LinkSet.includes(:links).where(content_id: scope.pluck(:content_id)).destroy_all - Location.where(content_item: scope).delete_all - State.where(content_item: scope).delete_all - Translation.where(content_item: scope).delete_all - UserFacingVersion.where(content_item: scope).delete_all + Location.where(edition: scope).delete_all + State.where(edition: scope).delete_all + Translation.where(edition: scope).delete_all + UserFacingVersion.where(edition: scope).delete_all LockVersion.where(target: scope).delete_all scope.delete_all end diff --git a/config/initializers/lock_version_polymorphic_patch.rb b/config/initializers/lock_version_polymorphic_patch.rb new file mode 100644 index 000000000..47b8d8096 --- /dev/null +++ b/config/initializers/lock_version_polymorphic_patch.rb @@ -0,0 +1,44 @@ +# These are monkey patches to work around us changing one of our model classes +# from ContentItem to Edition. +# +# These are required because on LockVersion these are stored as a polymorphic +# association and thus the class is stored in the database. These monkey patches +# change the logic so that the database still stores a "ContentItem" string +# and loads Edition objects + +ActiveRecord::Associations::BelongsToPolymorphicAssociation.class_eval do + # When we load up an polymorphic association we will switch the type to be + # Edition when it is an instance of ContentItem + def klass + type = owner[reflection.foreign_type] + type = "Edition" if type == "ContentItem" + type.presence && type.constantize + end + + + # When saving a record we will replace the name of the target to be + # ContentItem if it is set to be Edition, this allows us to roll back. + alias_method :original_replace_keys, :replace_keys + + def replace_keys(record) + original_replace_keys(record) + owner[reflection.foreign_type] = "ContentItem" if owner[reflection.foreign_type] == "Edition" + end +end + +ActiveRecord::PredicateBuilder::AssociationQueryHandler.class_eval do + # This is a patch to have query scopes convert a find for a type of Edition + # to actually search for a type of "ContentItem" + def call(_, value) + queries = {} + + table = value.associated_table + if value.base_class + class_name = value.base_class == Edition ? "ContentItem" : value.base_class.name + queries[table.association_foreign_type.to_s] = class_name + end + + queries[table.association_foreign_key.to_s] = value.ids + predicate_builder.build_from_hash(queries) + end +end diff --git a/config/routes.rb b/config/routes.rb index f7cdda692..9dbca8615 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,8 @@ Rails.application.routes.draw do + def content_id_constraint(request) + UuidValidator.valid?(request.params[:content_id]) + end + scope format: false do put "/publish-intent(/*base_path)", to: "publish_intents#create_or_update" get "/publish-intent(/*base_path)", to: "publish_intents#show" @@ -10,18 +14,20 @@ namespace :v2 do get "/content", to: "content_items#index" - put "/content/:content_id", to: "content_items#put_content" - get "/content/:content_id", to: "content_items#show" - post "/content/:content_id/publish", to: "content_items#publish" - post "/content/:content_id/unpublish", to: "content_items#unpublish" - post "/content/:content_id/discard-draft", to: "content_items#discard_draft" - - get "/links/:content_id", to: "link_sets#get_links" - get "/expanded-links/:content_id", to: "link_sets#expanded_links" - patch "/links/:content_id", to: "link_sets#patch_links" - # put is provided for backwards compatibility. - put "/links/:content_id", to: "link_sets#patch_links" - get "/linked/:content_id", to: "link_sets#get_linked" + scope constraints: method(:content_id_constraint) do + put "/content/:content_id", to: "content_items#put_content" + get "/content/:content_id", to: "content_items#show" + post "/content/:content_id/publish", to: "content_items#publish" + post "/content/:content_id/unpublish", to: "content_items#unpublish" + post "/content/:content_id/discard-draft", to: "content_items#discard_draft" + + get "/links/:content_id", to: "link_sets#get_links" + get "/expanded-links/:content_id", to: "link_sets#expanded_links" + patch "/links/:content_id", to: "link_sets#patch_links" + # put is provided for backwards compatibility. + put "/links/:content_id", to: "link_sets#patch_links" + get "/linked/:content_id", to: "link_sets#get_linked" + end get "/linkables", to: "content_items#linkables" get "/new-linkables", to: "content_items#new_linkables" diff --git a/db/migrate/helpers/delete_content_item.rb b/db/migrate/helpers/delete_content.rb similarity index 58% rename from db/migrate/helpers/delete_content_item.rb rename to db/migrate/helpers/delete_content.rb index 87c38cf0a..9b1c2e5f5 100644 --- a/db/migrate/helpers/delete_content_item.rb +++ b/db/migrate/helpers/delete_content.rb @@ -1,17 +1,19 @@ module Helpers - module DeleteContentItem - def self.destroy_content_items_with_links(content_ids) + module DeleteContent + def self.destroy_document_with_links(content_ids) content_ids = Array(content_ids) - content_items = ContentItem.where(content_id: content_ids) - destroy_supporting_objects(content_items) - content_items.destroy_all + Document.where(content_id: content_ids).each do |document| + destroy_supporting_objects(documents.editions) + document.editions.destroy_all + document.destroy + end destroy_links(content_ids) end - def self.destroy_supporting_objects(content_items) - content_items = Array(content_items) + def self.destroy_edition_supporting_objects(editions) + editions = Array(editions) supporting_classes = [ AccessLimit, @@ -25,10 +27,10 @@ def self.destroy_supporting_objects(content_items) supporting_classes.each do |klass| next unless ActiveRecord::Base.connection.data_source_exists?(klass.table_name) - klass.where(content_item: content_items).destroy_all + klass.where(edition: editions).destroy_all end - LockVersion.where(target: content_items).destroy_all + LockVersion.where(target: editions).destroy_all end def self.destroy_links(content_ids) diff --git a/lib/data_hygiene/govspeak_compare.rb b/lib/data_hygiene/govspeak_compare.rb index 5dd7052b8..c1c08a234 100644 --- a/lib/data_hygiene/govspeak_compare.rb +++ b/lib/data_hygiene/govspeak_compare.rb @@ -2,10 +2,10 @@ module DataHygiene class GovspeakCompare - attr_reader :content_item + attr_reader :edition - def initialize(content_item) - @content_item = content_item + def initialize(edition) + @edition = edition end def published_html @@ -15,8 +15,8 @@ def published_html def generated_html @generated_html ||= html_from_details( Presenters::DetailsPresenter.new( - content_item.details_for_govspeak_conversion, - Presenters::ChangeHistoryPresenter.new(content_item) + edition.details_for_govspeak_conversion, + Presenters::ChangeHistoryPresenter.new(web_content_item) ).details ) end @@ -36,6 +36,10 @@ def pretty_much_same_html? private + def web_content_item + @web_content_item ||= Queries::GetWebContentItems.find(edition.id) + end + def calculate_diffs keys = (published_html.keys + generated_html.keys).uniq.sort keys.each_with_object({}) do |key, memo| @@ -66,7 +70,7 @@ def apply_old_html_common_changes(html) # This method strips out a lot of the common differences that are the result # of different versions of govspeak rendering. # It was wrote when testing Specialist Publisher documents and may need - # additional items when comparing content items published from different apps + # additional items when comparing editions published from different apps def basically_match(s) s = s.dup # strip span surrounding an inline-attachment as this element @@ -79,7 +83,7 @@ def basically_match(s) end def format_published_html - html_details = html_from_details(content_item.details) + html_details = html_from_details(edition.details) html_details.each_with_object({}) do |(key, value), memo| # pushed through nokogiri to catch minor html differences (
->
, unicode characters) memo[key] = Nokogiri::HTML.fragment(value).to_html diff --git a/lib/queue_publisher.rb b/lib/queue_publisher.rb index 53bfbe766..10d53571b 100644 --- a/lib/queue_publisher.rb +++ b/lib/queue_publisher.rb @@ -15,14 +15,14 @@ def connection class PublishFailedError < StandardError end - def send_message(content_item, routing_key: nil) + def send_message(edition, routing_key: nil) return if @noop - routing_key ||= routing_key(content_item) - publish_message(routing_key, content_item, content_type: 'application/json', persistent: true) + routing_key ||= routing_key(edition) + publish_message(routing_key, edition, content_type: 'application/json', persistent: true) end - def routing_key(content_item) - normalised = content_item.symbolize_keys + def routing_key(edition) + normalised = edition.symbolize_keys "#{normalised[:schema_name]}.#{normalised[:update_type]}" end diff --git a/lib/requeue_content.rb b/lib/requeue_content.rb index bb6a7b7d0..deb1e1a50 100644 --- a/lib/requeue_content.rb +++ b/lib/requeue_content.rb @@ -7,21 +7,21 @@ def initialize(number_of_items: nil) def call if number_of_items.present? - ContentItem.where(state: :published).limit(number_of_items).each do |content_item| - publish_to_queue(content_item) + Edition.where(state: :published).limit(number_of_items).each do |edition| + publish_to_queue(edition) end else - ContentItem.where(state: :published).find_each do |content_item| - publish_to_queue(content_item) + Edition.where(state: :published).find_each do |edition| + publish_to_queue(edition) end end end private - def publish_to_queue(content_item) + def publish_to_queue(edition) downstream_presenter = Presenters::DownstreamPresenter.new( - Queries::GetWebContentItems.find(content_item.id), + Queries::GetWebContentItems.find(edition.id), state_fallback_order: [:published] ) queue_payload = Presenters::MessageQueuePresenter.present( diff --git a/lib/tasks/data_cleanup.rake b/lib/tasks/data_cleanup.rake deleted file mode 100644 index c11b5efb8..000000000 --- a/lib/tasks/data_cleanup.rake +++ /dev/null @@ -1,48 +0,0 @@ -namespace :data_cleanup do - desc "Removes drafts for specialist publisher documents if they're duplicates" - task remove_substitutions: :environment do - attributes = Unpublishing - .where(type: "substitute") - .joins(:content_item) - .pluck(:content_item_id, :content_id) - - content_item_ids, content_ids = attributes - .flatten - .partition - .with_index { |_, index| index.even? } - - puts "Removing #{content_item_ids.count} content items" - - supporting_classes = [ - AccessLimit, - Linkable, - Location, - State, - Translation, - Unpublishing, - UserFacingVersion - ] - - supporting_classes.each do |klass| - puts "-- Removing all associated #{klass} objects" - klass.where(content_item_id: content_item_ids).destroy_all - end - - LockVersion.where( - target_id: content_item_ids, - target_type: "ContentItem" - ).destroy_all - - ContentItem.where(id: content_item_ids).destroy_all - - puts "Checking link sets" - content_ids.each do |content_id| - # Remove linkset if there's no content items left - # for that content ID. - unless ContentItem.exists?(content_id: content_id) - puts "-- Removing orphaned LinkSet for content ID '#{content_id}'" - LinkSet.where(content_id: content_id).destroy_all - end - end - end -end diff --git a/lib/tasks/data_sanitizer.rb b/lib/tasks/data_sanitizer.rb index b72085354..a510917c9 100644 --- a/lib/tasks/data_sanitizer.rb +++ b/lib/tasks/data_sanitizer.rb @@ -2,8 +2,8 @@ module Tasks class DataSanitizer def self.delete_access_limited(stdout) AccessLimit.all.each do |access_limit| - limited_draft = access_limit.content_item - stdout.puts "Discarding access limited draft content item '#{limited_draft.content_id}'" + limited_draft = access_limit.edition + stdout.puts "Discarding access limited draft edition '#{limited_draft.document.content_id}'" simulated_payload = limited_draft.as_json.symbolize_keys Commands::V2::DiscardDraft.call(simulated_payload, downstream: true) end diff --git a/lib/tasks/events.rake b/lib/tasks/events.rake index 0955398ce..a7650a2ba 100644 --- a/lib/tasks/events.rake +++ b/lib/tasks/events.rake @@ -15,9 +15,9 @@ namespace :events do puts "Imported #{imported} event#{imported == 1 ? '' : 's'} successfully 🍾" end - # $ EVENT_LOG_AWS_ACCESS_ID=AKIAIOSFODNN7EXAMPLE EVENT_LOG_AWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY EVENT_LOG_AWS_BUCKETNAME=govuk-publishing-api-event-log-integration S3_EXPORT_REGION=eu-west-1 rake 'events:import_content_item_events[content_id]' - desc "import all events for a content item" - task :import_content_item_events, [:content_id] => :environment do |_, args| + # $ EVENT_LOG_AWS_ACCESS_ID=AKIAIOSFODNN7EXAMPLE EVENT_LOG_AWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY EVENT_LOG_AWS_BUCKETNAME=govuk-publishing-api-event-log-integration S3_EXPORT_REGION=eu-west-1 rake 'events:import_content_id_events[content_id]' + desc "import all events matching a content_id" + task :import_content_id_events, [:content_id] => :environment do |_, args| event_dates = Event.where(content_id: args[:content_id]).where("payload IS NULL").pluck(:created_at) importer = Events::S3Importer.new s3_keys = event_dates.map do |event_date| diff --git a/lib/tasks/govspeak.rake b/lib/tasks/govspeak.rake index 1ff0fdd23..a51b97608 100644 --- a/lib/tasks/govspeak.rake +++ b/lib/tasks/govspeak.rake @@ -1,24 +1,20 @@ namespace :govspeak do task :compare, [:publishing_app, :limit, :offset, :order] => :environment do |_, args| args.with_defaults(order: "content_items.id ASC") - scope = State.joins(:content_item) - scope = scope.where(content_items: { publishing_app: args[:publishing_app] }) if args[:publishing_app].present? - scope = scope.where(name: %w(published unpublished draft)) + scope = Edition.where(state: %w(published unpublished draft)) + scope = scope.where(publishing_app: args[:publishing_app]) if args[:publishing_app].present? scope = scope.limit(args[:limit]) if args[:limit].present? scope = scope.offset(args[:offset]) if args[:offset].present? scope = scope.order(args[:order]) total = scope.count same_html = 0 trivial_differences = 0 - scope.each do |state| - content_item = state.content_item - comparer = DataHygiene::GovspeakCompare.new(content_item) + scope.each do |edition| + comparer = DataHygiene::GovspeakCompare.new(edition) same_html += 1 if comparer.same_html? trivial_differences += 1 if !comparer.same_html? && comparer.pretty_much_same_html? next if comparer.pretty_much_same_html? - state = State.where(content_item_id: content_item.id).pluck(:name).first - base_path = Location.where(content_item_id: content_item.id).pluck(:base_path).first - puts "Content Item #{content_item.id} #{content_item.content_id} #{state} #{base_path}" + puts "Edition #{edition.id} #{edition.document.content_id} #{edition.state} #{edition.base_path}" comparer.diffs.each do |field, diff| next if diff == [] puts field diff --git a/lib/tasks/queue.rake b/lib/tasks/queue.rake index 41d4da5af..519c72af8 100644 --- a/lib/tasks/queue.rake +++ b/lib/tasks/queue.rake @@ -28,7 +28,7 @@ namespace :queue do end end - desc "Add published content items to the message queue, optionally specifying a limit on the number of items" + desc "Add published editions to the message queue, optionally specifying a limit on the number of items" task :requeue_content, [:number_of_items] => :environment do |_, args| RequeueContent.new(number_of_items: args[:number_of_items]).call end diff --git a/lib/tasks/represent_downstream.rake b/lib/tasks/represent_downstream.rake index 650259343..0013ad3d5 100644 --- a/lib/tasks/represent_downstream.rake +++ b/lib/tasks/represent_downstream.rake @@ -1,8 +1,8 @@ namespace :represent_downstream do - desc "Represent all content_items downstream" + desc "Represent all editions downstream" task all: :environment do Commands::V2::RepresentDownstream.new.call( - ContentItem.where("document_type != 'travel_advice'").pluck(:content_id) + Edition.where("document_type != 'travel_advice'").pluck(:content_id) ) end @@ -13,7 +13,7 @@ namespace :represent_downstream do " task :document_type, [:document_type] => :environment do |_t, args| document_type = args[:document_type] - content_ids = ContentItem.where(document_type: document_type).pluck(:content_id) + content_ids = Edition.where(document_type: document_type).pluck(:content_id) Commands::V2::RepresentDownstream.new.call(content_ids) end @@ -24,7 +24,7 @@ namespace :represent_downstream do " task :rendering_app, [:rendering_app] => :environment do |_t, args| rendering_app = args[:rendering_app] - content_ids = ContentItem.where(rendering_app: rendering_app).pluck(:content_id) + content_ids = Edition.where(rendering_app: rendering_app).pluck(:content_id) Commands::V2::RepresentDownstream.new.call(content_ids) end @@ -35,12 +35,12 @@ namespace :represent_downstream do " task :publishing_app, [:publishing_app] => :environment do |_t, args| publishing_app = args[:publishing_app] - content_ids = ContentItem.where(publishing_app: publishing_app) + content_ids = Edition.where(publishing_app: publishing_app) Commands::V2::RepresentDownstream.new.call(content_ids) end desc " - Represent an individual content_item downstream + Represent an individual edition downstream Usage rake 'represent_downstream:content_id[57a1253c-68d3-4a93-bb47-b67b9b4f6b9a]' " diff --git a/lib/tasks/set_last_edited_at.rake b/lib/tasks/set_last_edited_at.rake index 28f26af65..f7ea8bd3d 100644 --- a/lib/tasks/set_last_edited_at.rake +++ b/lib/tasks/set_last_edited_at.rake @@ -1,4 +1,4 @@ -desc "Sets last_edited_at for each content item with the value from public_updated_at" +desc "Sets last_edited_at for each edition with the value from public_updated_at" task set_last_edited_at: :environment do sql = "UPDATE content_items SET last_edited_at = public_updated_at;" diff --git a/lib/tasks/validate.rake b/lib/tasks/validate.rake index 92b3719f5..042979941 100644 --- a/lib/tasks/validate.rake +++ b/lib/tasks/validate.rake @@ -4,7 +4,7 @@ namespace :db do Tasks::DatabaseRecordValidator.validate end - desc "Validates the version sequence for all content items in the database" + desc "Validates the version sequence for all editions in the database" task validate_versions: :environment do Tasks::VersionValidator.validate end diff --git a/lib/tasks/version_validator.rb b/lib/tasks/version_validator.rb index 0feeab839..40e177a37 100644 --- a/lib/tasks/version_validator.rb +++ b/lib/tasks/version_validator.rb @@ -46,18 +46,19 @@ def validate def query <<-SQL - select - content_id, - locale, + SELECT + documents.content_id, + documents.locale, -- select arrays of supporting attributes array_agg(state) as states, array_agg(user_facing_version) as versions, array_agg(base_path) as base_paths, - array_agg(id) as content_item_ids + array_agg(content_items.id) as content_item_ids - from content_items - group by content_id, locale + FROM content_items + JOIN documents ON documents.id = content_items.document_id + GROUP BY documents.content_id, documents.locale SQL end diff --git a/spec/commands/v2/discard_draft_spec.rb b/spec/commands/v2/discard_draft_spec.rb index 02e17d606..bc04d8cde 100644 --- a/spec/commands/v2/discard_draft_spec.rb +++ b/spec/commands/v2/discard_draft_spec.rb @@ -8,35 +8,39 @@ end let(:expected_content_store_payload) { { base_path: "/vat-rates" } } - let(:content_id) { SecureRandom.uuid } - let(:locale) { "en" } + let(:document) do + FactoryGirl.create(:document, + content_id: SecureRandom.uuid, + locale: "en", + stale_lock_version: stale_lock_version, + ) + end + let(:stale_lock_version) { 1 } let(:base_path) { "/vat-rates" } - let(:payload) { { content_id: content_id } } + let(:payload) { { content_id: document.content_id } } before do allow(Presenters::ContentStorePresenter).to receive(:present) .and_return(expected_content_store_payload) end - context "when a draft content item exists for the given content_id" do + context "when a draft edition exists for the given content_id" do let(:user_facing_version) { 2 } let!(:existing_draft_item) do - FactoryGirl.create(:access_limited_draft_content_item, - content_id: content_id, + FactoryGirl.create(:access_limited_draft_edition, + document: document, base_path: base_path, - locale: locale, - lock_version: 5, user_facing_version: user_facing_version, ) end - let!(:change_note) { ChangeNote.create(content_item: existing_draft_item) } + let!(:change_note) { ChangeNote.create(edition: existing_draft_item) } it "deletes the draft item" do expect { described_class.call(payload) - }.to change(ContentItem, :count).by(-1) + }.to change(Edition, :count).by(-1) - expect(ContentItem.exists?(id: existing_draft_item.id)).to eq(false) + expect(Edition.exists?(id: existing_draft_item.id)).to eq(false) end it "creates an action" do @@ -44,8 +48,8 @@ described_class.call(payload) expect(Action.count).to be 1 expect(Action.first.attributes).to match a_hash_including( - "content_id" => content_id, - "locale" => locale, + "content_id" => document.content_id, + "locale" => document.locale, "action" => "DiscardDraft", "content_item_id" => existing_draft_item.id, ) @@ -54,13 +58,13 @@ it "deletes the supporting objects for the draft item" do described_class.call(payload) - state = State.find_by(content_item: existing_draft_item) - translation = Translation.find_by(content_item: existing_draft_item) - location = Location.find_by(content_item: existing_draft_item) - access_limit = AccessLimit.find_by(content_item: existing_draft_item) - user_facing_version = UserFacingVersion.find_by(content_item: existing_draft_item) + state = State.find_by(edition: existing_draft_item) + translation = Translation.find_by(edition: existing_draft_item) + location = Location.find_by(edition: existing_draft_item) + access_limit = AccessLimit.find_by(edition: existing_draft_item) + user_facing_version = UserFacingVersion.find_by(edition: existing_draft_item) lock_version = LockVersion.find_by(target: existing_draft_item) - change_notes = ChangeNote.where(content_item: existing_draft_item) + change_notes = ChangeNote.where(edition: existing_draft_item) expect(state).to be_nil expect(translation).to be_nil @@ -75,7 +79,11 @@ expect(DownstreamDiscardDraftWorker).to receive(:perform_async_in_queue) .with( "downstream_high", - a_hash_including(base_path: base_path, content_id: content_id, locale: locale), + a_hash_including( + base_path: base_path, + content_id: document.content_id, + locale: document.locale, + ), ) described_class.call(payload) @@ -94,8 +102,7 @@ context "when the draft's lock version differs from the given lock version" do before do - lock_version = LockVersion.find_by!(target: existing_draft_item) - payload[:previous_version] = lock_version.number - 1 + payload[:previous_version] = document.stale_lock_version - 1 end it "raises an error" do @@ -105,23 +112,20 @@ end end - context "a published content item exists with the same base_path" do + context "a published edition exists with the same base_path" do + let(:stale_lock_version) { 3 } let!(:published_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, - lock_version: 3, + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, - locale: locale, user_facing_version: user_facing_version - 1, ) end it "increments the lock version of the published item" do - published_lock_version = LockVersion.find_by!(target: published_item) - expect { described_class.call(payload) - }.to change { published_lock_version.reload.number }.to(4) + }.to change { document.reload.stale_lock_version }.to(4) end it "it uses the downstream discard draft worker" do @@ -130,8 +134,8 @@ DownstreamDiscardDraftWorker::HIGH_QUEUE, a_hash_including( base_path: base_path, - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, ), ) described_class.call(payload) @@ -140,11 +144,11 @@ it "deletes the supporting objects for the draft item" do described_class.call(payload) - state = State.find_by(content_item: existing_draft_item) - translation = Translation.find_by(content_item: existing_draft_item) - location = Location.find_by(content_item: existing_draft_item) - access_limit = AccessLimit.find_by(content_item: existing_draft_item) - user_facing_version = UserFacingVersion.find_by(content_item: existing_draft_item) + state = State.find_by(edition: existing_draft_item) + translation = Translation.find_by(edition: existing_draft_item) + location = Location.find_by(edition: existing_draft_item) + access_limit = AccessLimit.find_by(edition: existing_draft_item) + user_facing_version = UserFacingVersion.find_by(edition: existing_draft_item) lock_version = LockVersion.find_by(target: existing_draft_item) expect(state).to be_nil @@ -158,17 +162,15 @@ it "deletes the draft" do expect { described_class.call(payload) - }.to change(ContentItem, :count).by(-1) + }.to change(Edition, :count).by(-1) end end - context "a published content item exists with a different base_path" do + context "a published edition exists with a different base_path" do let!(:published_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, - lock_version: 3, + FactoryGirl.create(:live_edition, + document: document, base_path: "/hat-rates", - locale: locale, user_facing_version: user_facing_version - 1, ) end @@ -179,20 +181,19 @@ DownstreamDiscardDraftWorker::HIGH_QUEUE, a_hash_including( base_path: base_path, - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, ), ) described_class.call(payload) end end - context "an unpublished content item exits" do + context "an unpublished edition exits" do let(:unpublished_item) do - FactoryGirl.create(:unpublished_content_item, + FactoryGirl.create(:unpublished_edition, + document: document, base_path: base_path, - content_id: content_id, - locale: locale, user_facing_version: user_facing_version - 1, ) end @@ -203,8 +204,8 @@ DownstreamDiscardDraftWorker::HIGH_QUEUE, a_hash_including( base_path: base_path, - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, ), ) described_class.call(payload) @@ -212,11 +213,13 @@ end context "when a locale is provided in the payload" do + let(:french_document) do + FactoryGirl.create(:document, content_id: document.content_id, locale: "fr") + end let!(:french_draft_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: french_document, base_path: "#{base_path}.fr", - locale: "fr", ) end @@ -227,32 +230,30 @@ it "deletes the draft for the given locale" do expect { described_class.call(payload) - }.to change(ContentItem, :count).by(-1) + }.to change(Edition, :count).by(-1) - expect(ContentItem.exists?(id: french_draft_item.id)).to eq(false), - "The French draft item was not removed" + expect(Edition.where(id: french_draft_item.id)).not_to exist end - it "does not delete the english content item" do + it "does not delete the english edition" do described_class.call(payload) - expect(ContentItem.exists?(id: existing_draft_item.id)).to eq(true), - "The English draft item was removed" + expect(Edition.where(id: existing_draft_item.id)).to exist end end it_behaves_like TransactionalCommand end - context "when no draft content item exists for the given content_id" do + context "when no draft edition exists for the given content_id" do it "raises a command error with code 404" do expect { described_class.call(payload) }.to raise_error(CommandError) do |error| expect(error.code).to eq(404) end end - context "and a published content item exists" do + context "and a published edition exists" do before do - FactoryGirl.create(:live_content_item, content_id: content_id) + FactoryGirl.create(:live_edition, document: document) end it "raises a command error with code 422" do diff --git a/spec/commands/v2/patch_link_set_spec.rb b/spec/commands/v2/patch_link_set_spec.rb index 9bd991fc2..8d6def71b 100644 --- a/spec/commands/v2/patch_link_set_spec.rb +++ b/spec/commands/v2/patch_link_set_spec.rb @@ -84,10 +84,7 @@ link_set = LinkSet.last expect(link_set).to be_present - - lock_version = LockVersion.find_by(target: link_set) - expect(lock_version).to be_present - expect(lock_version.number).to eq(1) + expect(link_set.stale_lock_version).to eq(1) end it "responds with a success object containing the newly created links in the same order as in the request" do @@ -179,10 +176,7 @@ link_set = LinkSet.last expect(link_set).to be_present - - lock_version = LockVersion.find_by(target: link_set) - expect(lock_version).to be_present - expect(lock_version.number).to eq(2) + expect(link_set.stale_lock_version).to eq(2) end it "responds with a success object containing the updated links in the same order as in the request" do @@ -225,10 +219,10 @@ end end - context "when a draft content item exists for the content_id" do - let!(:draft_content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + context "when a draft edition exists for the content_id" do + before do + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: "/some-path", title: "Some Title", ) @@ -251,17 +245,16 @@ described_class.call(payload.merge(bulk_publishing: true)) end - context "when a draft content item has multiple translations" do - let!(:french_draft_content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + context "when a draft edition has multiple translations" do + before do + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id, locale: "fr"), base_path: "/french-path", title: "French Title", - locale: "fr", ) end - it "sends the draft content items for all locales downstream" do + it "sends the draft editions for all locales downstream" do %w(en fr).each do |locale| expect(DownstreamDraftWorker).to receive(:perform_async_in_queue) .with( @@ -286,10 +279,10 @@ end end - context "when a live content item exists for the content_id" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, + context "when a live edition exists for the content_id" do + before do + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: "/some-path", title: "Some Title", ) @@ -325,17 +318,16 @@ described_class.call(payload.merge(bulk_publishing: true)) end - context "when a live content item has multiple translations" do - let!(:french_live_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, + context "when a live edition has multiple translations" do + before do + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id, locale: "fr"), base_path: "/french-path", title: "French Title", - locale: "fr", ) end - it "sends the live content item for all locales downstream" do + it "sends the live edition for all locales downstream" do %w(en fr).each do |locale| expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with( @@ -364,10 +356,10 @@ end end - context "when an unpublished content item exists for the content_id" do - let!(:unpublished_content_item) do - FactoryGirl.create(:unpublished_content_item, - content_id: content_id, + context "when an unpublished edition exists for the content_id" do + before do + FactoryGirl.create(:unpublished_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: "/some-path", title: "Some Title", ) diff --git a/spec/commands/v2/post_action_spec.rb b/spec/commands/v2/post_action_spec.rb index 6266ae04e..7c9dcb5c3 100644 --- a/spec/commands/v2/post_action_spec.rb +++ b/spec/commands/v2/post_action_spec.rb @@ -2,15 +2,20 @@ RSpec.describe Commands::V2::PostAction do describe ".call" do - let(:content_id) { SecureRandom.uuid } - let(:locale) { "en" } + let(:document) do + FactoryGirl.create(:document, + content_id: SecureRandom.uuid, + locale: "en", + stale_lock_version: 6, + ) + end let(:action) { "FactCheck" } let(:draft) { nil } let(:payload) do { - content_id: content_id, - locale: locale, + content_id: document.content_id, + locale: document.locale, action: action, draft: draft, } @@ -64,15 +69,8 @@ end end - context "when a draft content item exists" do - before do - FactoryGirl.create( - :draft_content_item, - content_id: content_id, - locale: locale, - lock_version: 6, - ) - end + context "when a draft edition exists" do + before { FactoryGirl.create(:draft_edition, document: document) } include_examples "action behaviour" context "and we specify the action is not for a draft" do @@ -81,15 +79,8 @@ end end - context "when a published content item exists" do - before do - FactoryGirl.create( - :live_content_item, - content_id: content_id, - locale: locale, - lock_version: 6, - ) - end + context "when a published edition exists" do + before { FactoryGirl.create(:live_edition, document: document) } let(:draft) { false } @@ -101,7 +92,7 @@ end end - context "when no content item exists" do + context "when no edition exists" do include_examples "raises a 404 command error" end end diff --git a/spec/commands/v2/publish_spec.rb b/spec/commands/v2/publish_spec.rb index a4e5f5eeb..c92535e69 100644 --- a/spec/commands/v2/publish_spec.rb +++ b/spec/commands/v2/publish_spec.rb @@ -6,19 +6,21 @@ let(:locale) { "en" } let(:user_facing_version) { 5 } + let!(:document) do + FactoryGirl.create(:document, + locale: locale, + stale_lock_version: 2) + end + let!(:draft_item) do - FactoryGirl.create( - :draft_content_item, - content_id: content_id, - lock_version: 2, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, - locale: locale, user_facing_version: user_facing_version, ) end let(:expected_content_store_payload) { { base_path: base_path } } - let(:content_id) { SecureRandom.uuid } before do stub_request(:put, %r{.*content-store.*/content/.*}) @@ -32,7 +34,7 @@ let(:payload) do { - content_id: content_id, + content_id: document.content_id, update_type: "major", previous_version: 2, } @@ -43,12 +45,12 @@ payload.delete(:update_type) end - context "with an update_type stored on the draft content item" do + context "with an update_type stored on the draft edition" do before do draft_item.update_attributes!(update_type: "major") end - it "uses the update_type from the draft content item" do + it "uses the update_type from the draft edition" do expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with("downstream_high", hash_including(message_queue_update_type: "major")) @@ -56,7 +58,7 @@ end end - context "without an update_type stored on the draft content item" do + context "without an update_type stored on the draft edition" do before do draft_item.update_attributes!(update_type: nil) end @@ -69,12 +71,12 @@ end end - context "when the content item was previously published" do + context "when the edition was previously published" do let(:existing_base_path) { base_path } let!(:live_item) do - FactoryGirl.create(:live_content_item, - content_id: draft_item.content_id, + FactoryGirl.create(:live_edition, + document: document, base_path: existing_base_path, user_facing_version: user_facing_version - 1, ) @@ -83,15 +85,15 @@ it "marks the previously published item as 'superseded'" do described_class.call(payload) - new_item = ContentItem.find(live_item.id) + new_item = Edition.find(live_item.id) expect(new_item.state).to eq("superseded") end end - context "when the content item was previously unpublished" do + context "when the edition was previously unpublished" do let!(:live_item) do - FactoryGirl.create(:unpublished_content_item, - content_id: draft_item.content_id, + FactoryGirl.create(:unpublished_edition, + document: draft_item.document, base_path: base_path, user_facing_version: user_facing_version - 1, ) @@ -100,29 +102,29 @@ it "marks the previously unpublished item as 'superseded'" do described_class.call(payload) - new_item = ContentItem.find(live_item.id) + new_item = Edition.find(live_item.id) expect(new_item.state).to eq("superseded") end end - context "with another content item blocking the publish action" do - let(:draft_locale) { draft_item.locale } + context "with another edition blocking the publish action" do + let(:draft_locale) { document.locale } - let!(:other_content_item) do - FactoryGirl.create(:redirect_live_content_item, - locale: draft_locale, + let!(:other_edition) do + FactoryGirl.create(:redirect_live_edition, + document: FactoryGirl.create(:document, locale: draft_locale), base_path: base_path, ) end - it "unpublishes the content item which is in the way" do + it "unpublishes the edition which is in the way" do described_class.call(payload) - updated_other_content_item = ContentItem.find(other_content_item.id) + updated_other_edition = Edition.find(other_edition.id) - expect(updated_other_content_item.state).to eq("unpublished") - expect(updated_other_content_item.locale).to eq(draft_locale) - expect(updated_other_content_item.base_path).to eq(base_path) + expect(updated_other_edition.state).to eq("unpublished") + expect(updated_other_edition.document.locale).to eq(draft_locale) + expect(updated_other_edition.base_path).to eq(base_path) end end @@ -142,7 +144,7 @@ it "changes the state of the draft item to 'published'" do described_class.call(payload) - updated_draft_item = ContentItem.find(draft_item.id) + updated_draft_item = Edition.find(draft_item.id) expect(updated_draft_item.state).to eq("published") end @@ -162,7 +164,7 @@ described_class.call(payload) expect(Action.count).to be 1 expect(Action.first.attributes).to match a_hash_including( - "content_id" => content_id, + "content_id" => document.content_id, "locale" => locale, "action" => "Publish", ) @@ -175,7 +177,7 @@ end end - context "with a public_updated_at set on the draft content item" do + context "with a public_updated_at set on the draft edition" do let(:public_updated_at) { Time.zone.now - 1.year } before do @@ -189,7 +191,7 @@ end end - context "with no public_updated_at set on the draft content item" do + context "with no public_updated_at set on the draft edition" do before do draft_item.update_attributes!(public_updated_at: nil) end @@ -210,29 +212,29 @@ it "preserves the public_updated_at value from the last published item" do public_updated_at = Time.zone.now - 2.years - FactoryGirl.create(:live_content_item, - content_id: draft_item.content_id, + FactoryGirl.create(:live_edition, + document: draft_item.document, public_updated_at: public_updated_at, base_path: base_path, ) described_class.call(payload) - expect(ContentItem.last.public_updated_at.iso8601).to eq(public_updated_at.iso8601) + expect(Edition.last.public_updated_at.iso8601).to eq(public_updated_at.iso8601) end it "preserves the public_updated_at value from the last unpublished item" do public_updated_at = Time.zone.now - 2.years - FactoryGirl.create(:unpublished_content_item, - content_id: draft_item.content_id, + FactoryGirl.create(:unpublished_edition, + document: draft_item.document, public_updated_at: public_updated_at, base_path: base_path, ) described_class.call(payload) - expect(ContentItem.last.public_updated_at.iso8601).to eq(public_updated_at.iso8601) + expect(Edition.last.public_updated_at.iso8601).to eq(public_updated_at.iso8601) end end @@ -261,7 +263,7 @@ before do draft_item.update(update_type: "major") payload[:update_type] = "minor" - ChangeNote.create(content_item: draft_item) + ChangeNote.create(edition: draft_item) end it "deletes associated ChangeNote records" do expect { described_class.call(payload) } @@ -270,7 +272,7 @@ end end - context "with a first_published_at set on the draft content item" do + context "with a first_published_at set on the draft edition" do let(:first_published_at) { Time.zone.now - 1.year } before do @@ -284,7 +286,7 @@ end end - context "with no first_published_at set on the draft content item" do + context "with no first_published_at set on the draft edition" do before do draft_item.update_attributes!(first_published_at: nil) end @@ -298,14 +300,14 @@ context "when the base_path differs from the previously published item" do let!(:live_item) do - FactoryGirl.create(:live_content_item, - content_id: draft_item.content_id, + FactoryGirl.create(:live_edition, + document: draft_item.document, base_path: "/hat-rates", ) end before do - FactoryGirl.create(:redirect_draft_content_item, + FactoryGirl.create(:redirect_draft_edition, base_path: "/hat-rates", ) end @@ -313,9 +315,9 @@ it "publishes the redirect already created, from the old location to the new location" do described_class.call(payload) - redirect = ContentItem.find_by( + redirect = Edition.with_document.find_by( base_path: "/hat-rates", - locale: "en", + documents: { locale: "en" }, state: "published", ) @@ -326,14 +328,14 @@ it "supersedes the previously published item" do described_class.call(payload) - updated_item = ContentItem.find(live_item.id) + updated_item = Edition.find(live_item.id) expect(updated_item.state).to eq("superseded") end end - context "when an access limit is set on the draft content item" do + context "when an access limit is set on the draft edition" do before do - FactoryGirl.create(:access_limit, content_item: draft_item) + FactoryGirl.create(:access_limit, edition: draft_item) end it "destroys the access limit" do @@ -341,7 +343,7 @@ described_class.call(payload) }.to change(AccessLimit, :count).by(-1) - expect(AccessLimit.exists?(content_item: draft_item)).to eq(false) + expect(AccessLimit.exists?(edition: draft_item)).to eq(false) end end @@ -370,8 +372,8 @@ context "but a published item does exist" do before do - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, ) end @@ -379,7 +381,7 @@ it "raises an error to indicate it has already been published" do expect { described_class.call(payload) - }.to raise_error(CommandError, /already published content item/) + }.to raise_error(CommandError, /already published edition/) end end end @@ -387,10 +389,9 @@ it_behaves_like TransactionalCommand end - context "for a pathless content item format" do - let(:pathless_content_item) do - FactoryGirl.create( - :draft_content_item, + context "for a pathless edition format" do + let(:pathless_edition) do + FactoryGirl.create(:draft_edition, document_type: "contact", user_facing_version: 2, base_path: nil, @@ -399,7 +400,7 @@ let(:payload) do { - content_id: pathless_content_item.content_id, + content_id: pathless_edition.document.content_id, update_type: "major", previous_version: 1, } @@ -409,15 +410,14 @@ it "publishes the item" do described_class.call(payload) - updated_item = ContentItem.find(pathless_content_item.id) + updated_item = Edition.find(pathless_edition.id) expect(updated_item.state).to eq("published") end context "with a previously published item" do - let!(:live_content_item) do - FactoryGirl.create( - :live_content_item, - content_id: pathless_content_item.content_id, + let!(:live_edition) do + FactoryGirl.create(:live_edition, + document: pathless_edition.document, document_type: "contact", user_facing_version: 1, ) @@ -426,7 +426,7 @@ it "publishes the draft" do described_class.call(payload) - updated_item = ContentItem.find(pathless_content_item.id) + updated_item = Edition.find(pathless_edition.id) expect(updated_item.state).to eq("published") end end diff --git a/spec/commands/v2/put_content_spec.rb b/spec/commands/v2/put_content_spec.rb index ab9dbc5c5..ff1f1a00c 100644 --- a/spec/commands/v2/put_content_spec.rb +++ b/spec/commands/v2/put_content_spec.rb @@ -107,50 +107,37 @@ end end - context "when creating a draft for a previously published content item" do + context "when creating a draft for a previously published edition" do let(:first_published_at) { 1.year.ago } before do - FactoryGirl.create(:live_content_item, - content_id: content_id, - lock_version: 2, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id, stale_lock_version: 5), user_facing_version: 5, first_published_at: first_published_at, base_path: base_path, ) end - it "creates the draft's lock version using the live's lock version as a starting point" do - described_class.call(payload) - - content_item = ContentItem.last - - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.state).to eq("draft") - expect(content_item.content_store).to eq("draft") - expect(LockVersion.find_by!(target: content_item).number).to eq(3) - end - it "creates the draft's user-facing version using the live's user-facing version as a starting point" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.state).to eq("draft") - expect(content_item.user_facing_version).to eq(6) + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) + expect(edition.state).to eq("draft") + expect(edition.user_facing_version).to eq(6) end it "copies over the first_published_at timestamp" do described_class.call(payload) - content_item = ContentItem.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) + edition = Edition.last + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) - expect(content_item.first_published_at.iso8601).to eq(first_published_at.iso8601) + expect(edition.first_published_at.iso8601).to eq(first_published_at.iso8601) end context "and the base path has changed" do @@ -164,13 +151,13 @@ it "sets the correct base path on the location" do described_class.call(payload) - expect(ContentItem.where(base_path: "/moved", state: "draft")).to exist + expect(Edition.where(base_path: "/moved", state: "draft")).to exist end it "creates a redirect" do described_class.call(payload) - redirect = ContentItem.find_by( + redirect = Edition.find_by( base_path: base_path, state: "draft", ) @@ -193,18 +180,18 @@ described_class.call(payload) end - context "when the locale differs from the existing draft content item" do + context "when the locale differs from the existing draft edition" do before do payload.merge!(locale: "fr", title: "French Title") end - it "creates a separate draft content item in the given locale" do + it "creates a separate draft edition in the given locale" do described_class.call(payload) - expect(ContentItem.count).to eq(2) + expect(Edition.count).to eq(2) - content_item = ContentItem.last - expect(content_item.title).to eq("French Title") - expect(content_item.locale).to eq("fr") + edition = Edition.last + expect(edition.title).to eq("French Title") + expect(edition.document.locale).to eq("fr") end end end @@ -218,25 +205,20 @@ described_class.call(payload) Commands::V2::Publish.call(content_id: content_id, update_type: "minor") - regex = /user_facing_version=(\d+) and locale=en for content item=#{Regexp.quote(content_id)} conflicts/ - - expect { - thread1 = Thread.new { described_class.call(payload) } - thread2 = Thread.new { described_class.call(payload) } - thread1.join - thread2.join - }.to raise_error(CommandError, regex) + thread1 = Thread.new { described_class.call(payload) } + thread2 = Thread.new { described_class.call(payload) } + thread1.join + thread2.join - expect(ContentItem.all.pluck(:state)).to eq %w(superseded published draft) + expect(Edition.all.pluck(:state)).to eq %w(superseded published draft) end end end - context "when creating a draft for a previously unpublished content item" do + context "when creating a draft for a previously unpublished edition" do before do - FactoryGirl.create(:unpublished_content_item, - content_id: content_id, - lock_version: 2, + FactoryGirl.create(:unpublished_edition, + document: FactoryGirl.create(:document, content_id: content_id, stale_lock_version: 2), user_facing_version: 5, base_path: base_path, ) @@ -245,23 +227,23 @@ it "creates the draft's lock version using the unpublished lock version as a starting point" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.state).to eq("draft") - expect(LockVersion.find_by!(target: content_item).number).to eq(3) + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) + expect(edition.state).to eq("draft") + expect(edition.document.stale_lock_version).to eq(3) end it "creates the draft's user-facing version using the unpublished user-facing version as a starting point" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.state).to eq("draft") - expect(content_item.user_facing_version).to eq(6) + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) + expect(edition.state).to eq("draft") + expect(edition.user_facing_version).to eq(6) end it "allows the setting of first_published_at" do @@ -270,44 +252,43 @@ described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.first_published_at).to eq(explicit_first_published) + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) + expect(edition.first_published_at).to eq(explicit_first_published) end end - context "when the payload is for a brand new content item" do - it "creates a content item" do + context "when the payload is for a brand new edition" do + it "creates an edition" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item).to be_present - expect(content_item.content_id).to eq(content_id) - expect(content_item.title).to eq("Some Title") + expect(edition).to be_present + expect(edition.document.content_id).to eq(content_id) + expect(edition.title).to eq("Some Title") end - it "sets a draft state for the content item" do + it "sets a draft state for the edition" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item.state).to eq("draft") + expect(edition.state).to eq("draft") end - it "sets a user-facing version of 1 for the content item" do + it "sets a user-facing version of 1 for the edition" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item.user_facing_version).to eq(1) + expect(edition.user_facing_version).to eq(1) end - it "creates a lock version for the content item" do + it "creates a lock version for the edition" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - lock_version = LockVersion.find_by!(target: content_item) - expect(lock_version.number).to eq(1) + expect(edition.document.stale_lock_version).to eq(1) end it "creates a change note" do @@ -316,18 +297,20 @@ end end - context "when the payload is for an already drafted content item" do + context "when the payload is for an already drafted edition" do + let(:document) do + FactoryGirl.create(:document, content_id: content_id, stale_lock_version: 1) + end let!(:previously_drafted_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, title: "Old Title", - lock_version: 1, publishing_app: "publisher", ) end - it "updates the content item" do + it "updates the edition" do described_class.call(payload) previously_drafted_item.reload @@ -352,19 +335,17 @@ expect(previously_drafted_item.first_published_at.iso8601).to eq(first_published_at.iso8601) end - it "does not increment the user-facing version for the content item" do + it "does not increment the user-facing version for the edition" do described_class.call(payload) previously_drafted_item.reload expect(previously_drafted_item.user_facing_version).to eq(1) end - it "increments the lock version for the content item" do + it "increments the lock version for the document" do described_class.call(payload) - previously_drafted_item.reload - lock_version = LockVersion.find_by!(target: previously_drafted_item) - expect(lock_version.number).to eq(2) + expect(document.reload.stale_lock_version).to eq(2) end context "when the base path has changed" do @@ -385,7 +366,7 @@ it "creates a redirect" do described_class.call(payload) - redirect = ContentItem.find_by( + redirect = Edition.find_by( base_path: "/old-path", state: "draft", ) @@ -414,29 +395,27 @@ described_class.call(payload) end - context "when the locale differs from the existing draft content item" do + context "when the locale differs from the existing draft edition" do before do payload.merge!(locale: "fr", title: "French Title") end - it "creates a separate draft content item in the given locale" do + it "creates a separate draft edition in the given locale" do described_class.call(payload) - expect(ContentItem.count).to eq(2) + expect(Edition.count).to eq(2) - content_item = ContentItem.last - expect(content_item.title).to eq("French Title") + edition = Edition.last + expect(edition.title).to eq("French Title") - expect(content_item.locale).to eq("fr") + expect(edition.document.locale).to eq("fr") end end context "when there is a draft at the new base path" do let!(:substitute_item) do - FactoryGirl.create(:draft_content_item, - content_id: SecureRandom.uuid, + FactoryGirl.create(:draft_edition, base_path: base_path, title: "Substitute Content", - lock_version: 1, publishing_app: "publisher", document_type: "coming_soon", ) @@ -444,14 +423,12 @@ it "deletes the substitute item" do described_class.call(payload) - expect(ContentItem.exists?(id: substitute_item.id)).to eq(false) + expect(Edition.exists?(id: substitute_item.id)).to eq(false) end context "conflicting version" do before do - lock_version = LockVersion.find_by!(target: previously_drafted_item) - lock_version.update_attributes!(number: 2) - + previously_drafted_item.document.update!(stale_lock_version: 2) payload.merge!(previous_version: 1) end @@ -459,7 +436,7 @@ expect { described_class.call(payload) }.to raise_error(CommandError, /Conflict/) - expect(ContentItem.exists?(id: substitute_item.id)).to eq(true) + expect(Edition.exists?(id: substitute_item.id)).to eq(true) end end end @@ -467,9 +444,7 @@ context "with a 'previous_version' which does not match the current lock_version of the draft item" do before do - lock_version = LockVersion.find_by!(target: previously_drafted_item) - lock_version.update_attributes!(number: 2) - + previously_drafted_item.document.update!(stale_lock_version: 2) payload.merge!(previous_version: 1) end @@ -489,17 +464,17 @@ it "resets those attributes to their defaults from the database" do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item.redirects).to eq([]) - expect(content_item.phase).to eq("live") - expect(content_item.locale).to eq("en") + expect(edition.redirects).to eq([]) + expect(edition.phase).to eq("live") + expect(edition.document.locale).to eq("en") end end context "when the previous draft has an access limit" do let!(:access_limit) do - FactoryGirl.create(:access_limit, content_item: previously_drafted_item, users: ["old-user"]) + FactoryGirl.create(:access_limit, edition: previously_drafted_item, users: ["old-user"]) end context "when the params includes an access limit" do @@ -535,7 +510,7 @@ described_class.call(payload) }.to change(AccessLimit, :count).by(1) - access_limit = AccessLimit.find_by!(content_item: previously_drafted_item) + access_limit = AccessLimit.find_by!(edition: previously_drafted_item) expect(access_limit.users).to eq(["new-user"]) end end @@ -554,7 +529,7 @@ access_limit = AccessLimit.last expect(access_limit.users).to eq(["new-user"]) - expect(access_limit.content_item).to eq(ContentItem.last) + expect(access_limit.edition).to eq(Edition.last) end end @@ -576,10 +551,9 @@ }.to change(LinkSet, :count).by(1) link_set = LinkSet.last - lock_version = LockVersion.find_by(target: link_set) - expect(lock_version).to be_present - expect(lock_version.number).to eq(1) + expect(link_set).to be_present + expect(link_set.stale_lock_version).to eq(1) end end @@ -674,13 +648,11 @@ it "stores the provided timestamp" do last_edited_at = 1.year.ago - described_class.call(payload.merge( - last_edited_at: last_edited_at - )) + described_class.call(payload.merge(last_edited_at: last_edited_at)) - content_item = ContentItem.last + edition = Edition.last - expect(content_item.last_edited_at.iso8601).to eq(last_edited_at.iso8601) + expect(edition.last_edited_at.iso8601).to eq(last_edited_at.iso8601) end end @@ -688,17 +660,17 @@ Timecop.freeze do described_class.call(payload) - content_item = ContentItem.last + edition = Edition.last - expect(content_item.last_edited_at.iso8601).to eq(Time.zone.now.iso8601) + expect(edition.last_edited_at.iso8601).to eq(Time.zone.now.iso8601) end end end context "when the draft does exist" do - let!(:content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + let!(:edition) do + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id) ) end @@ -708,14 +680,16 @@ it "stores the provided timestamp" do last_edited_at = 1.year.ago - described_class.call(payload.merge( - update_type: update_type, - last_edited_at: last_edited_at - )) + described_class.call( + payload.merge( + update_type: update_type, + last_edited_at: last_edited_at, + ) + ) - content_item.reload + edition.reload - expect(content_item.last_edited_at.iso8601).to eq(last_edited_at.iso8601) + expect(edition.last_edited_at.iso8601).to eq(last_edited_at.iso8601) end end end @@ -725,28 +699,26 @@ Timecop.freeze do described_class.call(payload) - content_item.reload + edition.reload - expect(content_item.last_edited_at.iso8601).to eq(Time.zone.now.iso8601) + expect(edition.last_edited_at.iso8601).to eq(Time.zone.now.iso8601) end end context "when other update type" do it "dosen't change last_edited_at" do - old_last_edited_at = content_item.last_edited_at + old_last_edited_at = edition.last_edited_at - described_class.call(payload.merge( - update_type: "republish" - )) + described_class.call(payload.merge(update_type: "republish")) - content_item.reload + edition.reload - expect(content_item.last_edited_at).to eq(old_last_edited_at) + expect(edition.last_edited_at).to eq(old_last_edited_at) end end end - context "with a pathless content item payload" do + context "with a pathless edition payload" do before do payload.delete(:base_path) payload[:schema_name] = "contact" @@ -755,7 +727,7 @@ it "saves the content as draft" do expect { described_class.call(payload) - }.to change(ContentItem, :count).by(1) + }.to change(Edition, :count).by(1) end it "sends to the downstream draft worker" do @@ -763,26 +735,32 @@ described_class.call(payload) end - context "for an existing draft content item" do - let!(:draft_content_item) do - FactoryGirl.create(:draft_content_item, content_id: content_id, title: "Old Title") + context "for an existing draft edition" do + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), + title: "Old Title" + ) end it "updates the draft" do described_class.call(payload) - expect(draft_content_item.reload.title).to eq("Some Title") + expect(draft_edition.reload.title).to eq("Some Title") end end - context "for an existing live content item" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, content_id: content_id, title: "Old Title") + context "for an existing live edition" do + let!(:live_edition) do + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), + title: "Old Title" + ) end it "creates a new draft" do expect { described_class.call(payload) - }.to change(ContentItem, :count).by(1) + }.to change(Edition, :count).by(1) end end end @@ -792,7 +770,6 @@ payload.merge!( schema_name: "contact", document_type: "contact", - previous_version: 2, ) end @@ -801,18 +778,16 @@ described_class.call(payload) end - # This covers a specific edge case where the content item uniqueness validator + # This covers a specific edge case where the edition uniqueness validator # matched anything else with the same state, locale and version because it # was previously ignoring the base path, now it should return without # attempting to validate for pathless formats. context "with other similar pathless items" do before do - FactoryGirl.create(:draft_content_item, - content_id: SecureRandom.uuid, + FactoryGirl.create(:draft_edition, base_path: nil, schema_name: "contact", document_type: "contact", - locale: "en", user_facing_version: 3, ) end @@ -824,14 +799,12 @@ end end - context "when there's a conflicting content item" do + context "when there's a conflicting edition" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, base_path: base_path, schema_name: "contact", document_type: "contact", - locale: "en", user_facing_version: 3, ) end @@ -839,7 +812,7 @@ it "conflicts" do expect { described_class.call(payload) - }.to raise_error(CommandError, /Conflict/) + }.to raise_error(CommandError, /base path=\/vat-rates conflicts/) end end end diff --git a/spec/commands/v2/represent_downstream_spec.rb b/spec/commands/v2/represent_downstream_spec.rb index 2073a0ab7..c2c0c109c 100644 --- a/spec/commands/v2/represent_downstream_spec.rb +++ b/spec/commands/v2/represent_downstream_spec.rb @@ -6,40 +6,45 @@ end describe "call" do - let(:locale_content_id) { SecureRandom.uuid } before do - 2.times { FactoryGirl.create(:draft_content_item) } - FactoryGirl.create(:live_content_item, content_id: locale_content_id, document_type: "guidance", locale: "en") - FactoryGirl.create(:live_content_item, content_id: locale_content_id, document_type: "guidance", locale: "fr") - FactoryGirl.create(:live_content_item, document_type: "press_release") + 2.times { FactoryGirl.create(:draft_edition) } + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, locale: "en"), + document_type: "guidance", + ) + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, locale: "fr"), + document_type: "guidance", + ) + FactoryGirl.create(:live_edition, document_type: "press_release") end context "downstream live" do it "sends to downstream live worker" do expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .exactly(3).times - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end it "uses 'downstream_low' queue" do expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with("downstream_low", anything) .at_least(1).times - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end it "doesn't update dependencies" do expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with(anything, a_hash_including(update_dependencies: false)) .at_least(1).times - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end it "has a message_queue_update_type of 'links'" do expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with(anything, a_hash_including(message_queue_update_type: "links")) .at_least(1).times - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end it "updates for each locale" do @@ -49,7 +54,7 @@ expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with(anything, a_hash_including(locale: "fr")) .exactly(1).times - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end end @@ -58,7 +63,10 @@ expect(DownstreamLiveWorker).to receive(:perform_async_in_queue) .with("downstream_low", a_hash_including(:content_id, :locale, :payload_version)) .exactly(2).times - subject.call(ContentItem.where(document_type: "guidance").pluck(:content_id), false) + subject.call( + Edition.with_document.where(document_type: "guidance").pluck('documents.content_id'), + false, + ) end end @@ -70,12 +78,12 @@ a_hash_including(:content_id, :locale, :payload_version, update_dependencies: false) ) .exactly(5).times - subject.call(ContentItem.pluck(:content_id), true) + subject.call(Document.pluck(:content_id), true) end it "can not send to downstream draft worker" do expect(DownstreamDraftWorker).to_not receive(:perform_async_in_queue) - subject.call(ContentItem.pluck(:content_id), false) + subject.call(Document.pluck(:content_id), false) end end end diff --git a/spec/commands/v2/unpublish_spec.rb b/spec/commands/v2/unpublish_spec.rb index e583ff7c3..6e64ac48d 100644 --- a/spec/commands/v2/unpublish_spec.rb +++ b/spec/commands/v2/unpublish_spec.rb @@ -4,6 +4,12 @@ let(:content_id) { SecureRandom.uuid } let(:base_path) { "/vat-rates" } let(:locale) { "en" } + let(:document) do + FactoryGirl.create(:document, + content_id: content_id, + locale: locale, + ) + end describe "call" do let(:payload) do @@ -22,11 +28,10 @@ end context "when unpublishing is invalid" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, + let!(:live_edition) do + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, - locale: locale, ) end @@ -70,26 +75,25 @@ end context "when the document is published" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, + let!(:live_edition) do + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, - locale: locale, ) end include_examples "creates an action" - it "sets the content item's state to `unpublished`" do + it "sets the edition's state to `unpublished`" do described_class.call(payload) - expect(live_content_item.reload.state).to eq("unpublished") + expect(live_edition.reload.state).to eq("unpublished") end it "creates an Unpublishing" do described_class.call(payload) - unpublishing = Unpublishing.find_by(content_item: live_content_item) + unpublishing = Unpublishing.find_by(edition: live_edition) expect(unpublishing.type).to eq("gone") expect(unpublishing.explanation).to eq("Removed for testing porpoises") expect(unpublishing.alternative_path).to eq("/new-path") @@ -120,7 +124,7 @@ it "rejects the request with a 404" do expect { described_class.call(payload_with_allow_draft) - }.to raise_error(CommandError, "Could not find a content item to unpublish") { |error| + }.to raise_error(CommandError, "Could not find an edition to unpublish") { |error| expect(error.code).to eq(404) } end @@ -140,7 +144,7 @@ it "ignores the provided unpublished_at" do described_class.call(payload) - unpublishing = Unpublishing.find_by(content_item: live_content_item) + unpublishing = Unpublishing.find_by(edition: live_edition) expect(unpublishing.unpublished_at).to be_nil end @@ -158,7 +162,7 @@ it "persists the provided unpublished_at" do described_class.call(payload) - unpublishing = Unpublishing.find_by(content_item: live_content_item) + unpublishing = Unpublishing.find_by(edition: live_edition) expect(unpublishing.unpublished_at).to eq DateTime.new(2016, 8, 1, 10, 10, 10) end end @@ -166,19 +170,17 @@ end context "when only a draft is present" do - let!(:draft_content_item) do - FactoryGirl.create( - :draft_content_item, - content_id: content_id, + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: document, user_facing_version: 3, - locale: locale, ) end it "rejects the request with a 404" do expect { described_class.call(payload) - }.to raise_error(CommandError, "Could not find a content item to unpublish") { |error| + }.to raise_error(CommandError, "Could not find an edition to unpublish") { |error| expect(error.code).to eq(404) } end @@ -193,16 +195,16 @@ let(:action_payload) { payload_with_allow_draft } include_examples "creates an action" - it "sets the content item's state to `unpublished`" do + it "sets the edition's state to `unpublished`" do described_class.call(payload_with_allow_draft) - expect(draft_content_item.reload.state).to eq("unpublished") + expect(draft_edition.reload.state).to eq("unpublished") end it "creates an Unpublishing" do described_class.call(payload_with_allow_draft) - unpublishing = Unpublishing.find_by(content_item: draft_content_item) + unpublishing = Unpublishing.find_by(edition: draft_edition) expect(unpublishing.type).to eq("gone") expect(unpublishing.explanation).to eq("Removed for testing porpoises") expect(unpublishing.alternative_path).to eq("/new-path") @@ -211,7 +213,7 @@ context "where there is an access limit" do before do AccessLimit.create!( - content_item: draft_content_item, + edition: draft_edition, users: [SecureRandom.uuid] ) end @@ -250,66 +252,75 @@ end end - context "when there is a previously unpublished content item" do - let!(:previous_content_item) do - FactoryGirl.create(:unpublished_content_item, - content_id: content_id, + context "when there is a previously unpublished edition" do + let!(:previous_edition) do + FactoryGirl.create(:unpublished_edition, + document: document, base_path: base_path, user_facing_version: 1, ) end + let(:french_document) do + FactoryGirl.create(:document, + content_id: document.content_id, + locale: "fr", + ) + end it "supersedes the unpublished item" do described_class.call(payload.merge(allow_draft: true)) - expect(previous_content_item.reload.state).to eq("superseded") + expect(previous_edition.reload.state).to eq("superseded") end it "does not supersede unpublished items in a different locale" do - t = ContentItem.find_by!(id: previous_content_item.id) - t.update!(locale: "fr") + Edition.find_by!(id: previous_edition.id) + .update(document: french_document) described_class.call(payload.merge(allow_draft: true)) - expect(previous_content_item.reload.state).to eq("unpublished") + expect(previous_edition.reload.state).to eq("unpublished") end end - context "when there is a previously published content item" do - let!(:previous_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, + context "when there is a previously published edition" do + let!(:previous_edition) do + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, - locale: locale, user_facing_version: 1, ) end + let(:french_document) do + FactoryGirl.create(:document, + content_id: document.content_id, + locale: "fr", + ) + end it "supersedes the published item" do described_class.call(payload.merge(allow_draft: true)) - expect(previous_content_item.reload.state).to eq("superseded") + expect(previous_edition.reload.state).to eq("superseded") end it "does not supersede published items in a different locale" do - t = ContentItem.find_by!(id: previous_content_item.id) - t.update!(locale: "fr") + Edition.find_by!(id: previous_edition.id) + .update(document: french_document) described_class.call(payload.merge(allow_draft: true)) - expect(previous_content_item.reload.state).to eq("published") + expect(previous_edition.reload.state).to eq("published") end end end end context "when the document is redrafted" do - let!(:live_content_item) do - FactoryGirl.create( - :live_content_item, + let!(:live_edition) do + FactoryGirl.create(:live_edition, :with_draft, - content_id: content_id, - locale: locale, + document: document, ) end @@ -337,28 +348,27 @@ it "discards the draft" do described_class.call(payload) - content_items = ContentItem.where(content_id: content_id) - expect(content_items.count).to eq(1) + editions = Edition.with_document.where("documents.content_id": content_id) - expect(content_items.last.state).to eq("unpublished") + expect(editions.count).to eq(1) + expect(editions.last.state).to eq("unpublished") end - it "unpublishes the content item" do + it "unpublishes the edition" do described_class.call(payload) - live_content_item.reload + live_edition.reload - unpublishing = Unpublishing.find_by(content_item: live_content_item) + unpublishing = Unpublishing.find_by(edition: live_edition) expect(unpublishing).not_to be_nil end end end context "when the document is already unpublished" do - let!(:unpublished_content_item) do - FactoryGirl.create(:unpublished_content_item, - content_id: content_id, + let!(:unpublished_edition) do + FactoryGirl.create(:unpublished_edition, + document: document, base_path: base_path, - locale: locale, explanation: "This explnatin has a typo", alternative_path: "/new-path", ) @@ -376,11 +386,11 @@ it "maintains the state of unpublished" do described_class.call(payload) - expect(unpublished_content_item.reload.state).to eq("unpublished") + expect(unpublished_edition.reload.state).to eq("unpublished") end it "updates the Unpublishing" do - unpublishing = Unpublishing.find_by(content_item: unpublished_content_item) + unpublishing = Unpublishing.find_by(edition: unpublished_edition) expect(unpublishing.explanation).to eq("This explnatin has a typo") described_class.call(payload) @@ -422,15 +432,14 @@ end context "when the unpublishing type is substitute" do - let!(:unpublished_content_item) do - FactoryGirl.create(:substitute_unpublished_content_item, - content_id: content_id, - locale: locale, + let!(:unpublished_edition) do + FactoryGirl.create(:substitute_unpublished_edition, + document: document, ) end it "rejects the request with a 404" do - message = "Could not find a content item to unpublish" + message = "Could not find an edition to unpublish" expect { described_class.call(payload) }.to raise_error(CommandError, message) { |error| @@ -442,10 +451,7 @@ context "with the `downstream` flag set to `false`" do before do - FactoryGirl.create(:live_content_item, :with_draft, - content_id: content_id, - locale: locale, - ) + FactoryGirl.create(:live_edition, :with_draft, document: document) end it "does not send to any downstream system for a 'gone'" do @@ -494,26 +500,25 @@ end context "when the document has no location" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, - content_id: content_id, - locale: locale, + let!(:live_edition) do + FactoryGirl.create(:live_edition, + document: document, base_path: nil, ) end include_examples "creates an action" - it "sets the content item's state to `unpublished`" do + it "sets the edition's state to `unpublished`" do described_class.call(payload) - expect(live_content_item.reload.state).to eq("unpublished") + expect(live_edition.reload.state).to eq("unpublished") end it "creates an Unpublishing" do described_class.call(payload) - unpublishing = Unpublishing.find_by(content_item: live_content_item) + unpublishing = Unpublishing.find_by(edition: live_edition) expect(unpublishing.type).to eq("gone") expect(unpublishing.explanation).to eq("Removed for testing porpoises") expect(unpublishing.alternative_path).to eq("/new-path") diff --git a/spec/controllers/v2/actions_controller_spec.rb b/spec/controllers/v2/actions_controller_spec.rb index b1023fe37..433fb640e 100644 --- a/spec/controllers/v2/actions_controller_spec.rb +++ b/spec/controllers/v2/actions_controller_spec.rb @@ -2,28 +2,26 @@ RSpec.describe V2::ActionsController do describe ".create" do - let(:content_id) { SecureRandom.uuid } - let(:locale) { "en" } + let(:document) do + FactoryGirl.create(:document, + content_id: SecureRandom.uuid, + locale: "en", + stale_lock_version: 5, + ) + end let(:action) { "FactCheck" } - let(:params) { { content_id: content_id, format: :json } } + let(:params) { { content_id: document.content_id, format: :json } } let(:payload) do { - locale: locale, + locale: document.locale, action: action, } end let(:json_payload) { payload.to_json } - context "when a content item exists" do - before do - FactoryGirl.create( - :draft_content_item, - content_id: content_id, - locale: locale, - lock_version: 5 - ) - end + context "when an edition exists" do + before { FactoryGirl.create(:draft_edition, document: document) } context "and the request is valid" do it "returns 201" do @@ -51,7 +49,7 @@ end end - context "when the content item does not exist" do + context "when the edition does not exist" do it "returns 404" do post(:create, params: params, body: json_payload) expect(response).to have_http_status(404) diff --git a/spec/controllers/v2/content_items_controller_spec.rb b/spec/controllers/v2/content_items_controller_spec.rb index a1453b9ff..7fff3ce89 100644 --- a/spec/controllers/v2/content_items_controller_spec.rb +++ b/spec/controllers/v2/content_items_controller_spec.rb @@ -5,18 +5,22 @@ let(:validator) do instance_double(SchemaValidator, valid?: true, errors: []) end + let(:document_en) do + FactoryGirl.create(:document, content_id: content_id, locale: "en") + end + let(:document_ar) do + FactoryGirl.create(:document, content_id: content_id, locale: "ar") + end before do allow(SchemaValidator).to receive(:new).and_return(validator) stub_request(:any, /content-store/) - @draft = FactoryGirl.create( - :draft_content_item, - content_id: content_id, + @draft = FactoryGirl.create(:draft_edition, + document: document_en, base_path: "/content.en", document_type: "topic", schema_name: "topic", - locale: "en", user_facing_version: 2, ) end @@ -24,28 +28,22 @@ describe "index" do before do @en_draft_content = @draft - @ar_draft_content = FactoryGirl.create( - :draft_content_item, - content_id: content_id, - locale: "ar", + @ar_draft_content = FactoryGirl.create(:draft_edition, + document: document_ar, base_path: "/content.ar", document_type: "topic", schema_name: "topic", user_facing_version: 2, ) - @en_live_content = FactoryGirl.create( - :live_content_item, - content_id: content_id, - locale: "en", + @en_live_content = FactoryGirl.create(:live_edition, + document: document_en, base_path: "/content.en", document_type: "topic", schema_name: "topic", user_facing_version: 1, ) - @ar_live_content = FactoryGirl.create( - :live_content_item, - content_id: content_id, - locale: "ar", + @ar_live_content = FactoryGirl.create(:live_edition, + document: document_ar, base_path: "/content.ar", document_type: "topic", schema_name: "topic", @@ -56,21 +54,23 @@ context "searching a field" do context "when there is a valid query" do let(:previous_live_version) do - FactoryGirl.create(:superseded_content_item, - base_path: "/foo", - document_type: "topic", - schema_name: "topic", - title: "zip", - user_facing_version: 1) + FactoryGirl.create(:superseded_edition, + base_path: "/foo", + document_type: "topic", + schema_name: "topic", + title: "zip", + user_facing_version: 1, + ) end - let!(:content_item) do - FactoryGirl.create(:live_content_item, - base_path: "/foo", - content_id: previous_live_version.content_id, - document_type: "topic", - schema_name: "topic", - title: "bar", - user_facing_version: 2) + let!(:edition) do + FactoryGirl.create(:live_edition, + base_path: "/foo", + document: previous_live_version.document, + document_type: "topic", + schema_name: "topic", + title: "bar", + user_facing_version: 2, + ) end it "returns the item when searching for base_path" do @@ -99,7 +99,7 @@ expect(response.status).to eq(200) end - it "responds with the english content item as json" do + it "responds with the english edition as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.length).to eq(1) @@ -117,7 +117,7 @@ expect(response.status).to eq(200) end - it "responds with the specific locale content item as json" do + it "responds with the specific locale edition as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.length).to eq(1) @@ -141,7 +141,7 @@ expect(parsed_response_body.length).to eq(2) end - it "responds with all the localised content items as json" do + it "responds with all the localised editions as json" do base_paths = parsed_response_body.map { |item| item.fetch("base_path") } expect(base_paths.sort). to eq ["/content.en", "/content.ar"].sort end @@ -155,7 +155,7 @@ it "is successful" do expect(response.status).to eq(200) end - it "responds with the content item as json" do + it "responds with the edition as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.first.fetch("content_id")).to eq(content_id.to_s) end @@ -168,7 +168,7 @@ it "is successful" do expect(response.status).to eq(200) end - it "responds with the content item as json" do + it "responds with the edition as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.first.fetch("content_id")).to eq(content_id.to_s) end @@ -181,7 +181,7 @@ it "is successful" do expect(response.status).to eq(200) end - it "responds with the content item as json" do + it "responds with the edition as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.first.fetch("content_id")).to eq(content_id.to_s) end @@ -339,7 +339,7 @@ expect(response.status).to eq(200) end - it "responds with the content items for the given organistion as json" do + it "responds with the editions for the given organistion as json" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.first.fetch("content_id")).to eq(content_id.to_s) end @@ -347,7 +347,7 @@ end describe "show" do - context "for an existing content item" do + context "for an existing edition" do before do get :show, params: { content_id: content_id } end @@ -356,38 +356,30 @@ expect(response.status).to eq(200) end - it "responds with the content item as json" do + it "responds with the edition as json" do parsed_response_body = parsed_response expect(parsed_response_body.fetch("content_id")).to eq(content_id.to_s) end end - context "for a non-existent content item" do + context "for a non-existent edition" do it "responds with 404" do get :show, params: { content_id: SecureRandom.uuid } expect(response.status).to eq(404) end end - - context "for an invalid content ID" do - it "responds with 404" do - get :show, params: { content_id: "invalid" } - - expect(response.status).to eq(404) - end - end end describe "put_content" do - context "with valid request params for a new content item" do + context "with valid request params for a new edition" do before do - content_item_hash = @draft.as_json - content_item_hash = content_item_hash - .merge("base_path" => "/that-rates") - .merge("routes" => [{ "path" => "/that-rates", "type" => "exact" }]) + edition_hash = @draft.as_json.merge( + "base_path" => "/that-rates", + "routes" => [{ "path" => "/that-rates", "type" => "exact" }], + ) request.env["CONTENT_TYPE"] = "application/json" - request.env["RAW_POST_DATA"] = content_item_hash.to_json + request.env["RAW_POST_DATA"] = edition_hash.to_json put :put_content, params: { content_id: SecureRandom.uuid } end @@ -395,21 +387,21 @@ expect(response.status).to eq(200) end - it "responds with the content item" do + it "responds with the edition" do parsed_response_body = parsed_response expect(parsed_response_body["base_path"]).to eq("/that-rates") end end - context "with valid request params for an existing content item" do + context "with valid request params for an existing edition" do before do - content_item_hash = @draft.as_json - content_item_hash = content_item_hash - .merge("base_path" => "/that-rates") - .merge("routes" => [{ "path" => "/that-rates", "type" => "exact" }]) + edition_hash = @draft.as_json.merge( + "base_path" => "/that-rates", + "routes" => [{ "path" => "/that-rates", "type" => "exact" }], + ) request.env["CONTENT_TYPE"] = "application/json" - request.env["RAW_POST_DATA"] = content_item_hash.to_json + request.env["RAW_POST_DATA"] = edition_hash.to_json put :put_content, params: { content_id: content_id } end @@ -417,7 +409,7 @@ expect(response.status).to eq(200) end - it "responds with the content item" do + it "responds with the edition" do parsed_response_body = parsed_response expect(parsed_response_body["content_id"]).to eq(content_id) end @@ -437,7 +429,7 @@ end describe "publish" do - context "for an existing draft content item" do + context "for an existing draft edition" do before do request.env["CONTENT_TYPE"] = "application/json" request.env["RAW_POST_DATA"] = { update_type: "major" }.to_json @@ -455,11 +447,11 @@ end end - context "for a non-existent content item" do + context "for a non-existent edition" do it "responds with 404" do request.env["CONTENT_TYPE"] = "application/json" request.env["RAW_POST_DATA"] = { update_type: "major" }.to_json - post :publish, params: { content_id: "missing" } + post :publish, params: { content_id: SecureRandom.uuid } expect(response.status).to eq(404) end @@ -468,10 +460,10 @@ describe "index" do before do - FactoryGirl.create(:draft_content_item, publishing_app: 'publisher', base_path: '/content') - FactoryGirl.create(:draft_content_item, publishing_app: 'whitehall', base_path: '/item1') - FactoryGirl.create(:live_content_item, publishing_app: 'whitehall', base_path: '/item2') - FactoryGirl.create(:unpublished_content_item, publishing_app: 'specialist_publisher', base_path: '/item3') + FactoryGirl.create(:draft_edition, publishing_app: 'publisher', base_path: '/content') + FactoryGirl.create(:draft_edition, publishing_app: 'whitehall', base_path: '/item1') + FactoryGirl.create(:live_edition, publishing_app: 'whitehall', base_path: '/item2') + FactoryGirl.create(:unpublished_edition, publishing_app: 'specialist_publisher', base_path: '/item3') end it "displays items filtered by publishing_app parameter" do diff --git a/spec/controllers/v2/link_sets_controller_spec.rb b/spec/controllers/v2/link_sets_controller_spec.rb index 389ad56e6..6dbfcfd73 100644 --- a/spec/controllers/v2/link_sets_controller_spec.rb +++ b/spec/controllers/v2/link_sets_controller_spec.rb @@ -2,9 +2,10 @@ RSpec.describe V2::LinkSetsController do let(:content_id) { SecureRandom.uuid } + let(:document) { FactoryGirl.create(:document, content_id: content_id) } before do - FactoryGirl.create(:draft_content_item, content_id: content_id) + FactoryGirl.create(:draft_edition, document: document) stub_request(:any, /content-store/) end @@ -34,7 +35,7 @@ end end - context "for an existing content item" do + context "for an existing edition" do before do get :get_linked, params: { content_id: content_id, link_type: "topic", fields: ["content_id"] } end @@ -44,7 +45,7 @@ end end - context "for a non-existing content item" do + context "for a non-existing edition" do before do get :get_linked, params: { content_id: SecureRandom.uuid, link_type: "topic", fields: ["content_id"] } end diff --git a/spec/downstream_payload_spec.rb b/spec/downstream_payload_spec.rb index 4b337fd01..0caf14b21 100644 --- a/spec/downstream_payload_spec.rb +++ b/spec/downstream_payload_spec.rb @@ -2,8 +2,8 @@ RSpec.describe DownstreamPayload do def create_web_content_item(factory, factory_options = {}) - content_item = FactoryGirl.create(factory, factory_options) - Queries::GetWebContentItems.find(content_item.id) + edition = FactoryGirl.create(factory, factory_options) + Queries::GetWebContentItems.find(edition.id) end let(:payload_version) { 1 } @@ -17,7 +17,7 @@ def create_web_content_item(factory, factory_options = {}) } describe "#state" do - let(:web_content_item) { create_web_content_item(:live_content_item) } + let(:web_content_item) { create_web_content_item(:live_edition) } it "equals web_content_item.state" do expect(downstream_payload.state).to eq web_content_item.state @@ -25,16 +25,16 @@ def create_web_content_item(factory, factory_options = {}) end describe "#unpublished?" do - context "unpublished content item" do - let(:web_content_item) { create_web_content_item(:unpublished_content_item) } + context "unpublished edition" do + let(:web_content_item) { create_web_content_item(:unpublished_edition) } it "returns true" do expect(downstream_payload.unpublished?).to be true end end - context "published content item" do - let(:web_content_item) { create_web_content_item(:live_content_item) } + context "published edition" do + let(:web_content_item) { create_web_content_item(:live_edition) } it "returns false" do expect(downstream_payload.unpublished?).to be false @@ -44,7 +44,7 @@ def create_web_content_item(factory, factory_options = {}) describe "#content_store_action" do context "no base_path" do - let(:web_content_item) { create_web_content_item(:pathless_live_content_item) } + let(:web_content_item) { create_web_content_item(:pathless_live_edition) } it "returns :no_op" do expect(downstream_payload.content_store_action).to be :no_op @@ -52,7 +52,7 @@ def create_web_content_item(factory, factory_options = {}) end context "published item" do - let(:web_content_item) { create_web_content_item(:live_content_item) } + let(:web_content_item) { create_web_content_item(:live_edition) } it "returns :put" do expect(downstream_payload.content_store_action).to be :put @@ -60,7 +60,7 @@ def create_web_content_item(factory, factory_options = {}) end context "draft item" do - let(:web_content_item) { create_web_content_item(:draft_content_item) } + let(:web_content_item) { create_web_content_item(:draft_edition) } it "returns :put" do expect(downstream_payload.content_store_action).to be :put @@ -69,7 +69,7 @@ def create_web_content_item(factory, factory_options = {}) context "unpublished item" do context "withdrawn type" do - let(:web_content_item) { create_web_content_item(:withdrawn_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:withdrawn_unpublished_edition) } it "returns :put" do expect(downstream_payload.content_store_action).to be :put @@ -77,7 +77,7 @@ def create_web_content_item(factory, factory_options = {}) end context "redirect type" do - let(:web_content_item) { create_web_content_item(:redirect_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:redirect_unpublished_edition) } it "returns :put" do expect(downstream_payload.content_store_action).to be :put @@ -85,7 +85,7 @@ def create_web_content_item(factory, factory_options = {}) end context "gone type" do - let(:web_content_item) { create_web_content_item(:gone_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:gone_unpublished_edition) } it "returns :put" do expect(downstream_payload.content_store_action).to be :put @@ -93,7 +93,7 @@ def create_web_content_item(factory, factory_options = {}) end context "vanish type" do - let(:web_content_item) { create_web_content_item(:vanish_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:vanish_unpublished_edition) } it "returns :delete" do expect(downstream_payload.content_store_action).to be :delete @@ -112,7 +112,7 @@ def create_web_content_item(factory, factory_options = {}) } context "published item" do - let(:web_content_item) { create_web_content_item(:live_content_item) } + let(:web_content_item) { create_web_content_item(:live_edition) } it "returns a content store payload" do expect(downstream_payload.content_store_payload).to include(content_store_payload_hash) @@ -120,7 +120,7 @@ def create_web_content_item(factory, factory_options = {}) end context "draft item" do - let(:web_content_item) { create_web_content_item(:draft_content_item) } + let(:web_content_item) { create_web_content_item(:draft_edition) } it "returns a content store payload" do expect(downstream_payload.content_store_payload).to include(content_store_payload_hash) @@ -129,7 +129,7 @@ def create_web_content_item(factory, factory_options = {}) context "unpublished item" do context "withdrawn type" do - let(:web_content_item) { create_web_content_item(:withdrawn_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:withdrawn_unpublished_edition) } it "returns a content store payload" do expect(downstream_payload.content_store_payload).to include(content_store_payload_hash) @@ -137,7 +137,7 @@ def create_web_content_item(factory, factory_options = {}) end context "redirect type" do - let(:web_content_item) { create_web_content_item(:redirect_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:redirect_unpublished_edition) } it "returns a redirect payload" do expect(downstream_payload.content_store_payload).to include( @@ -148,7 +148,7 @@ def create_web_content_item(factory, factory_options = {}) end context "gone type" do - let(:web_content_item) { create_web_content_item(:gone_unpublished_content_item) } + let(:web_content_item) { create_web_content_item(:gone_unpublished_edition) } it "returns a gone payload" do expect(downstream_payload.content_store_payload).to include( @@ -161,7 +161,7 @@ def create_web_content_item(factory, factory_options = {}) end describe "#message_queue_payload" do - let(:web_content_item) { create_web_content_item(:live_content_item) } + let(:web_content_item) { create_web_content_item(:live_edition) } it "returns a message queue payload" do expect(downstream_payload.message_queue_payload("major")).to include( diff --git a/spec/event_logger_spec.rb b/spec/event_logger_spec.rb index 4bae82742..ab8a8c84a 100644 --- a/spec/event_logger_spec.rb +++ b/spec/event_logger_spec.rb @@ -34,26 +34,26 @@ end it "rolls back the transaction and retries if a CommandRetryableError is thrown" do - content_id = SecureRandom.uuid + document = FactoryGirl.create(:document) call_counter = 0 EventLogger.log_command(command_class, payload) do if call_counter == 0 - FactoryGirl.create(:live_content_item, content_id: content_id) + FactoryGirl.create(:live_edition, document: document) call_counter += 1 raise CommandRetryableError else # The original transaction should have been rolled back, so there should be no # corresponding ContentItem in the database - expect(ContentItem.where(content_id: content_id).count).to eq(0) - FactoryGirl.create(:live_content_item, content_id: content_id) + expect(Edition.where(document: document).count).to eq(0) + FactoryGirl.create(:live_edition, document: document) end end # The second time it was called, it should have succeeded and created an - # event and a content item + # event and an edition expect(Event.count).to eq(1) - expect(ContentItem.count).to eq(1) + expect(Edition.count).to eq(1) end it "retries five times in case if a CommandRetryableError is thrown, then raises CommandError" do diff --git a/spec/factories/document.rb b/spec/factories/document.rb new file mode 100644 index 000000000..aff731612 --- /dev/null +++ b/spec/factories/document.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :document do + content_id { SecureRandom.uuid } + locale "en" + stale_lock_version 1 + end +end diff --git a/spec/factories/draft_content_item.rb b/spec/factories/draft_content_item.rb deleted file mode 100644 index 3e751785a..000000000 --- a/spec/factories/draft_content_item.rb +++ /dev/null @@ -1,37 +0,0 @@ -FactoryGirl.define do - factory :draft_content_item, parent: :content_item do - state "draft" - content_store "draft" - end - - factory :redirect_draft_content_item, parent: :draft_content_item do - transient do - destination "/somewhere" - end - sequence(:base_path) { |n| "/test-redirect-#{n}" } - schema_name "redirect" - document_type "redirect" - routes [] - redirects { [{ 'path' => base_path, 'type' => 'exact', 'destination' => destination }] } - end - - factory :gone_draft_content_item, parent: :draft_content_item do - sequence(:base_path) { |n| "/dodo-sanctuary-#{n}" } - schema_name "gone" - document_type "gone" - end - - factory :access_limited_draft_content_item, parent: :draft_content_item do - sequence(:base_path) { |n| "/access-limited-#{n}" } - - after(:create) do |item, _| - FactoryGirl.create(:access_limit, content_item: item) - end - end - - factory :pathless_draft_content_item, parent: :draft_content_item do - base_path nil - schema_name "contact" - document_type "contact" - end -end diff --git a/spec/factories/content_item.rb b/spec/factories/edition.rb similarity index 67% rename from spec/factories/content_item.rb rename to spec/factories/edition.rb index 70f0d1229..53f0bacc1 100644 --- a/spec/factories/content_item.rb +++ b/spec/factories/edition.rb @@ -1,6 +1,6 @@ FactoryGirl.define do - factory :content_item do - content_id { SecureRandom.uuid } + factory :edition, aliases: [:draft_edition] do + document title "VAT rates" description "VAT rates for goods and services" schema_name "guide" @@ -27,24 +27,21 @@ } state "draft" content_store "draft" - locale "en" sequence(:base_path) { |n| "/vat-rates-#{n}" } user_facing_version 1 transient do - lock_version 1 change_note "note" end after(:create) do |item, evaluator| - FactoryGirl.create(:lock_version, number: evaluator.lock_version, target: item) unless item.update_type == "minor" || evaluator.change_note.nil? - FactoryGirl.create(:change_note, note: evaluator.change_note, content_item: item) + FactoryGirl.create(:change_note, note: evaluator.change_note, edition: item) end end end - factory :redirect_content_item, parent: :content_item do + factory :redirect_edition, aliases: [:redirect_draft_edition], parent: :edition do transient do destination "/somewhere" end @@ -55,9 +52,23 @@ redirects { [{ 'path' => base_path, 'type' => 'exact', 'destination' => destination }] } end - factory :gone_content_item, parent: :content_item do + factory :gone_edition, aliases: [:gone_draft_edition], parent: :edition do sequence(:base_path) { |n| "/dodo-sanctuary-#{n}" } schema_name "gone" document_type "gone" end + + factory :access_limited_edition, aliases: [:access_limited_draft_edition], parent: :edition do + sequence(:base_path) { |n| "/access-limited-#{n}" } + + after(:create) do |item, _| + FactoryGirl.create(:access_limit, edition: item) + end + end + + factory :pathless_edition, aliases: [:pathless_draft_edition], parent: :edition do + base_path nil + schema_name "contact" + document_type "contact" + end end diff --git a/spec/factories/live_content_item.rb b/spec/factories/live_edition.rb similarity index 61% rename from spec/factories/live_content_item.rb rename to spec/factories/live_edition.rb index 547ba1087..128b62246 100644 --- a/spec/factories/live_content_item.rb +++ b/spec/factories/live_edition.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :live_content_item, parent: :content_item do + factory :live_edition, parent: :edition do user_facing_version 1 state "published" content_store "live" @@ -9,12 +9,10 @@ end trait :with_draft do - after(:create) do |live_content_item, evaluator| - draft = FactoryGirl.create(:draft_content_item, - live_content_item.as_json(only: %i[title content_id schema_name document_type routes redirects]).merge( - locale: evaluator.locale, + after(:create) do |live_edition, evaluator| + draft = FactoryGirl.create(:draft_edition, + live_edition.as_json(only: %i[title document_id schema_name document_type routes redirects]).merge( base_path: evaluator.base_path, - lock_version: evaluator.lock_version, user_facing_version: evaluator.draft_version_number, ) ) @@ -28,7 +26,7 @@ end end - factory :redirect_live_content_item, parent: :live_content_item do + factory :redirect_live_edition, parent: :live_edition do sequence(:base_path) { |n| "/test-redirect-#{n}" } schema_name "redirect" document_type "redirect" @@ -36,20 +34,20 @@ redirects { [{ 'path' => base_path, 'type' => 'exact', 'destination' => '/somewhere' }] } end - factory :gone_live_content_item, parent: :live_content_item do + factory :gone_live_edition, parent: :live_edition do sequence(:base_path) { |n| "/dodo-sanctuary-#{n}" } schema_name "gone" document_type "gone" end - factory :coming_soon_live_content_item, parent: :live_content_item do + factory :coming_soon_live_edition, parent: :live_edition do schema_name "coming_soon" document_type "coming_soon" title "Coming soon" description "This item will be published soon" end - factory :pathless_live_content_item, parent: :live_content_item do + factory :pathless_live_edition, parent: :live_edition do base_path nil schema_name "contact" document_type "contact" diff --git a/spec/factories/superseded_content_item.rb b/spec/factories/superseded_edition.rb similarity index 53% rename from spec/factories/superseded_content_item.rb rename to spec/factories/superseded_edition.rb index 8d128f358..3243f02ef 100644 --- a/spec/factories/superseded_content_item.rb +++ b/spec/factories/superseded_edition.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :superseded_content_item, parent: :live_content_item do + factory :superseded_edition, parent: :live_edition do content_store nil state "superseded" end diff --git a/spec/factories/unpublished_content_item.rb b/spec/factories/unpublished_edition.rb similarity index 64% rename from spec/factories/unpublished_content_item.rb rename to spec/factories/unpublished_edition.rb index 342d98c0d..3056edf46 100644 --- a/spec/factories/unpublished_content_item.rb +++ b/spec/factories/unpublished_edition.rb @@ -1,5 +1,5 @@ FactoryGirl.define do - factory :unpublished_content_item, parent: :content_item, aliases: [:gone_unpublished_content_item] do + factory :unpublished_edition, parent: :edition, aliases: [:gone_unpublished_edition] do state "unpublished" content_store "live" transient do @@ -9,9 +9,9 @@ unpublished_at nil end - after(:create) do |content_item, evaluator| + after(:create) do |edition, evaluator| FactoryGirl.create(:unpublishing, - content_item: content_item, + edition: edition, type: evaluator.unpublishing_type, explanation: evaluator.explanation, alternative_path: evaluator.alternative_path, @@ -20,7 +20,7 @@ end end - factory :withdrawn_unpublished_content_item, parent: :unpublished_content_item do + factory :withdrawn_unpublished_edition, parent: :unpublished_edition do content_store 'live' transient do unpublishing_type "withdrawal" @@ -28,7 +28,7 @@ end end - factory :redirect_unpublished_content_item, parent: :unpublished_content_item do + factory :redirect_unpublished_edition, parent: :unpublished_edition do content_store 'live' transient do unpublishing_type "redirect" @@ -36,7 +36,7 @@ end end - factory :vanish_unpublished_content_item, parent: :unpublished_content_item do + factory :vanish_unpublished_edition, parent: :unpublished_edition do content_store 'live' transient do unpublishing_type "vanish" @@ -44,7 +44,7 @@ end end - factory :substitute_unpublished_content_item, parent: :unpublished_content_item do + factory :substitute_unpublished_edition, parent: :unpublished_edition do content_store nil transient do unpublishing_type "substitute" diff --git a/spec/factories/unpublishing.rb b/spec/factories/unpublishing.rb index cba83436c..d1c4dc116 100644 --- a/spec/factories/unpublishing.rb +++ b/spec/factories/unpublishing.rb @@ -1,6 +1,6 @@ FactoryGirl.define do factory :unpublishing do - content_item + edition type "gone" explanation "Removed for testing reasons" alternative_path "/new-path" diff --git a/spec/integration/draft_substitution_spec.rb b/spec/integration/draft_substitution_spec.rb index cc1dc5aba..2af8dc910 100644 --- a/spec/integration/draft_substitution_spec.rb +++ b/spec/integration/draft_substitution_spec.rb @@ -51,12 +51,12 @@ end it "discards the guide" do - expect(ContentItem.count).to eq(1) + expect(Edition.count).to eq(1) - content_item = ContentItem.first + edition = Edition.first - expect(content_item.document_type).to eq("gone") - expect(content_item.state).to eq("draft") + expect(edition.document_type).to eq("gone") + expect(edition.state).to eq("draft") end describe "after the second substitution" do @@ -65,12 +65,12 @@ end it "discards the gone" do - expect(ContentItem.count).to eq(1) + expect(Edition.count).to eq(1) - content_item = ContentItem.first + edition = Edition.first - expect(content_item.document_type).to eq("guide") - expect(content_item.state).to eq("draft") + expect(edition.document_type).to eq("guide") + expect(edition.state).to eq("draft") end describe "after the third substitution" do @@ -79,18 +79,18 @@ end it "discards the guide" do - expect(ContentItem.count).to eq(1) + expect(Edition.count).to eq(1) - content_item = ContentItem.first + edition = Edition.first - expect(content_item.document_type).to eq("gone") - expect(content_item.state).to eq("draft") + expect(edition.document_type).to eq("gone") + expect(edition.state).to eq("draft") end end end end - describe "putting a content item in a different locale" do + describe "putting an edition in a different locale" do let(:gone_base_path) { "/vat-rates-fr" } before do @@ -99,10 +99,10 @@ end it "does not discard the guide" do - expect(ContentItem.count).to eq(2) + expect(Edition.count).to eq(2) - guide_item = ContentItem.first - gone_item = ContentItem.second + guide_item = Edition.first + gone_item = Edition.second expect(guide_item.state).to eq("draft") expect(gone_item.state).to eq("draft") diff --git a/spec/integration/message_queue_publishing_spec.rb b/spec/integration/message_queue_publishing_spec.rb index b30b9ce7f..374a09aea 100644 --- a/spec/integration/message_queue_publishing_spec.rb +++ b/spec/integration/message_queue_publishing_spec.rb @@ -8,13 +8,13 @@ stub_content_store_calls(base_path) - content_item = generate_random_content_item(base_path, change_note) + edition = generate_random_edition(base_path, change_note) - put "/v2/content/#{content_id}", params: content_item.to_json + put "/v2/content/#{content_id}", params: edition.to_json expect(response).to be_ok - post "/v2/content/#{content_id}/publish", params: { locale: content_item["locale"] }.to_json + post "/v2/content/#{content_id}/publish", params: { locale: edition["locale"] }.to_json expect(response).to be_ok @@ -40,7 +40,7 @@ def stub_content_store_calls(base_path) .to_return(status: 200) end - def generate_random_content_item(base_path, change_note) + def generate_random_edition(base_path, change_note) random = GovukSchemas::RandomExample.for_schema(publisher_schema: "placeholder") if change_note diff --git a/spec/integration/pagination_spec.rb b/spec/integration/pagination_spec.rb index 5d94950ff..aae8bcea3 100644 --- a/spec/integration/pagination_spec.rb +++ b/spec/integration/pagination_spec.rb @@ -1,10 +1,9 @@ require "rails_helper" -RSpec.describe "Paging through content items" do +RSpec.describe "Paging through editions" do before do 5.times do |n| - FactoryGirl.create( - :draft_content_item, + FactoryGirl.create(:draft_edition, base_path: "/content-#{n}", document_type: "guide", schema_name: "guide", @@ -22,7 +21,7 @@ expect(response).to be_successful end - it "responds with content items in the correct order" do + it "responds with editions in the correct order" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.size).to eq(5) expect(parsed_response_body.first["base_path"]).to eq("/content-0") @@ -45,7 +44,7 @@ expect(response).to be_successful end - it "responds with content items limited by page_size" do + it "responds with editions limited by page_size" do parsed_response_body = parsed_response["results"] expect(parsed_response_body.size).to eq(2) expect(parsed_response_body.first["base_path"]).to eq("/content-3") diff --git a/spec/integration/redirecting_spec.rb b/spec/integration/redirecting_spec.rb index ea276634f..d18fd113e 100644 --- a/spec/integration/redirecting_spec.rb +++ b/spec/integration/redirecting_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Redirecting content items that are redrafted" do +RSpec.describe "Redirecting editions that are redrafted" do let(:put_content) { Commands::V2::PutContent } let(:publish) { Commands::V2::Publish } @@ -42,26 +42,26 @@ put_content.call(moved_payload) end - it "sets up the content items in the expected initial state" do - expect(ContentItem.count).to eq(3) - - content_item = ContentItem.first - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) - - content_item = ContentItem.second - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("draft") - expect(content_item.base_path).to eq("/bar") - expect(content_item.user_facing_version).to eq(2) - - content_item = ContentItem.third - expect(content_item.schema_name).to eq("redirect") - expect(content_item.state).to eq("draft") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) + it "sets up the editions in the expected initial state" do + expect(Edition.count).to eq(3) + + edition = Edition.first + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) + + edition = Edition.second + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("draft") + expect(edition.base_path).to eq("/bar") + expect(edition.user_facing_version).to eq(2) + + edition = Edition.third + expect(edition.schema_name).to eq("redirect") + expect(edition.state).to eq("draft") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) end context "when the item is published" do @@ -69,26 +69,26 @@ publish.call(publish_payload) end - it "transitions the states of the content items correctly" do - expect(ContentItem.count).to eq(3) - - content_item = ContentItem.first - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("superseded") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) - - content_item = ContentItem.second - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/bar") - expect(content_item.user_facing_version).to eq(2) - - content_item = ContentItem.third - expect(content_item.schema_name).to eq("redirect") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) + it "transitions the states of the editions correctly" do + expect(Edition.count).to eq(3) + + edition = Edition.first + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("superseded") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) + + edition = Edition.second + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/bar") + expect(edition.user_facing_version).to eq(2) + + edition = Edition.third + expect(edition.schema_name).to eq("redirect") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) end end end @@ -101,26 +101,26 @@ put_content.call(moved_payload) end - it "sets up the content items in the expected initial state" do - expect(ContentItem.count).to eq(3) - - content_item = ContentItem.first - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) - - content_item = ContentItem.second - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("draft") - expect(content_item.base_path).to eq("/bar") - expect(content_item.user_facing_version).to eq(2) - - content_item = ContentItem.third - expect(content_item.schema_name).to eq("redirect") - expect(content_item.state).to eq("draft") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) + it "sets up the editions in the expected initial state" do + expect(Edition.count).to eq(3) + + edition = Edition.first + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) + + edition = Edition.second + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("draft") + expect(edition.base_path).to eq("/bar") + expect(edition.user_facing_version).to eq(2) + + edition = Edition.third + expect(edition.schema_name).to eq("redirect") + expect(edition.state).to eq("draft") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) end context "when the redrafted item is published" do @@ -128,26 +128,26 @@ publish.call(publish_payload) end - it "transitions the states of the content items correctly" do - expect(ContentItem.count).to eq(3) - - content_item = ContentItem.first - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("superseded") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) - - content_item = ContentItem.second - expect(content_item.schema_name).to eq("guide") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/bar") - expect(content_item.user_facing_version).to eq(2) - - content_item = ContentItem.third - expect(content_item.schema_name).to eq("redirect") - expect(content_item.state).to eq("published") - expect(content_item.base_path).to eq("/foo") - expect(content_item.user_facing_version).to eq(1) + it "transitions the states of the editions correctly" do + expect(Edition.count).to eq(3) + + edition = Edition.first + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("superseded") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) + + edition = Edition.second + expect(edition.schema_name).to eq("guide") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/bar") + expect(edition.user_facing_version).to eq(2) + + edition = Edition.third + expect(edition.schema_name).to eq("redirect") + expect(edition.state).to eq("published") + expect(edition.base_path).to eq("/foo") + expect(edition.user_facing_version).to eq(1) end it "does not raise an error on subsequent redrafts and publishes" do diff --git a/spec/integration/reinstating_spec.rb b/spec/integration/reinstating_spec.rb index 4230f5c9b..77672a536 100644 --- a/spec/integration/reinstating_spec.rb +++ b/spec/integration/reinstating_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Reinstating Content Items that were previously unpublished" do +RSpec.describe "Reinstating editions that were previously unpublished" do let(:put_content_command) { Commands::V2::PutContent } let(:publish_command) { Commands::V2::Publish } @@ -59,7 +59,7 @@ allow(SchemaValidator).to receive(:new).and_return(validator) end - describe "after the content item is unpublished" do + describe "after the edition is unpublished" do before do 2.times do put_content_command.call(guide_draft_payload) @@ -70,12 +70,12 @@ publish_command.call(redirect_publish_payload) end - it "puts the content items into the correct states and versions" do - expect(ContentItem.count).to eq(3) + it "puts the editions into the correct states and versions" do + expect(Edition.count).to eq(3) - superseded1_item = ContentItem.first - superseded2_item = ContentItem.second - published_item = ContentItem.third + superseded1_item = Edition.first + superseded2_item = Edition.second + published_item = Edition.third expect(superseded1_item.state).to eq("superseded") expect(superseded2_item.state).to eq("unpublished") @@ -87,19 +87,19 @@ "The redirect should be regarded as a new piece of content" end - describe "after the original content item has been reinstated" do + describe "after the original edition has been reinstated" do before do put_content_command.call(guide_draft_payload) publish_command.call(guide_publish_payload) end - it "puts the content items into the correct states and versions" do - expect(ContentItem.count).to eq(4) + it "puts the editions into the correct states and versions" do + expect(Edition.count).to eq(4) - superseded1_item = ContentItem.first - superseded2_item = ContentItem.second - unpublished_item = ContentItem.third - published_item = ContentItem.fourth + superseded1_item = Edition.first + superseded2_item = Edition.second + unpublished_item = Edition.third + published_item = Edition.fourth expect(superseded1_item.state).to eq("superseded") expect(superseded2_item.state).to eq("superseded") @@ -112,20 +112,20 @@ expect(published_item.user_facing_version).to eq(3) end - describe "after the original content item has been superseded (again)" do + describe "after the original edition has been superseded (again)" do before do put_content_command.call(guide_draft_payload) publish_command.call(guide_publish_payload) end - it "puts the content items into the correct states and versions" do - expect(ContentItem.count).to eq(5) + it "puts the editions into the correct states and versions" do + expect(Edition.count).to eq(5) - superseded1_item = ContentItem.first - superseded2_item = ContentItem.second - unpublished_item = ContentItem.third - superseded3_item = ContentItem.fourth - published_item = ContentItem.fifth + superseded1_item = Edition.first + superseded2_item = Edition.second + unpublished_item = Edition.third + superseded3_item = Edition.fourth + published_item = Edition.fifth expect(superseded1_item.state).to eq("superseded") expect(superseded2_item.state).to eq("superseded") diff --git a/spec/integration/superseding_spec.rb b/spec/integration/superseding_spec.rb index 16fe93013..caac4bb57 100644 --- a/spec/integration/superseding_spec.rb +++ b/spec/integration/superseding_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Superseding Content Items" do +RSpec.describe "Superseding editions" do let(:put_content_command) { Commands::V2::PutContent } let(:publish_command) { Commands::V2::Publish } @@ -37,47 +37,47 @@ def call_commands describe "after the first pair is called" do before { call_commands } - it "creates and publishes a content item" do - expect(ContentItem.count).to eq(1) - content_item = ContentItem.first + it "creates and publishes an edition" do + expect(Edition.count).to eq(1) + edition = Edition.first - expect(content_item.state).to eq("published") - expect(content_item.user_facing_version).to eq(1) + expect(edition.state).to eq("published") + expect(edition.user_facing_version).to eq(1) end describe "after the second pair is called" do before { call_commands } - it "supersedes the previously published content item" do - expect(ContentItem.count).to eq(2) + it "supersedes the previously published edition" do + expect(Edition.count).to eq(2) - superseded_content_item = ContentItem.first - published_content_item = ContentItem.second + superseded_edition = Edition.first + published_edition = Edition.second - expect(superseded_content_item.state).to eq("superseded") - expect(published_content_item.state).to eq("published") + expect(superseded_edition.state).to eq("superseded") + expect(published_edition.state).to eq("published") - expect(superseded_content_item.user_facing_version).to eq(1) - expect(published_content_item.user_facing_version).to eq(2) + expect(superseded_edition.user_facing_version).to eq(1) + expect(published_edition.user_facing_version).to eq(2) end describe "after the third pair is called" do before { call_commands } - it "supersedes the previously published content item (again)" do - expect(ContentItem.count).to eq(3) + it "supersedes the previously published edition (again)" do + expect(Edition.count).to eq(3) - superseded1_content_item = ContentItem.first - superseded2_content_item = ContentItem.second - published_content_item = ContentItem.third + superseded1_edition = Edition.first + superseded2_edition = Edition.second + published_edition = Edition.third - expect(superseded1_content_item.state).to eq("superseded") - expect(superseded2_content_item.state).to eq("superseded") - expect(published_content_item.state).to eq("published") + expect(superseded1_edition.state).to eq("superseded") + expect(superseded2_edition.state).to eq("superseded") + expect(published_edition.state).to eq("published") - expect(superseded1_content_item.user_facing_version).to eq(1) - expect(superseded2_content_item.user_facing_version).to eq(2) - expect(published_content_item.user_facing_version).to eq(3) + expect(superseded1_edition.user_facing_version).to eq(1) + expect(superseded2_edition.user_facing_version).to eq(2) + expect(published_edition.user_facing_version).to eq(3) end end end diff --git a/spec/integration/unpublishing_spec.rb b/spec/integration/unpublishing_spec.rb index d2a591bd9..63fb015b1 100644 --- a/spec/integration/unpublishing_spec.rb +++ b/spec/integration/unpublishing_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Unpublishing Content Items" do +RSpec.describe "Unpublishing editions" do let(:put_content_command) { Commands::V2::PutContent } let(:publish_command) { Commands::V2::Publish } let(:unpublish_command) { Commands::V2::Unpublish } @@ -44,12 +44,11 @@ unpublish_command.call(unpublish_payload) end - it "unpublishes the content item" do - content_items = ContentItem.where(content_id: content_id) - expect(content_items.count).to eq(1) - - unpublished_item = content_items.last + it "unpublishes the edition" do + editions = Edition.with_document.where("documents.content_id": content_id) + expect(editions.count).to eq(1) + unpublished_item = editions.last expect(unpublished_item.state).to eq("unpublished") end @@ -60,12 +59,13 @@ unpublish_command.call(unpublish_payload) end - it "unpublishes the new content item and supersedes the old content item" do - content_items = ContentItem.where(content_id: content_id) - expect(content_items.count).to eq(2) + it "unpublishes the new edition and supersedes the old edition" do + editions = Edition.joins(:document) + .where("documents.content_id": content_id) + expect(editions.count).to eq(2) - superseded_item = content_items.first - unpublished_item = content_items.last + superseded_item = editions.first + unpublished_item = editions.last expect(superseded_item.state).to eq("superseded") expect(unpublished_item.state).to eq("unpublished") diff --git a/spec/lib/data_hygiene/govspeak_compare_spec.rb b/spec/lib/data_hygiene/govspeak_compare_spec.rb index 25487fb75..aca367bb4 100644 --- a/spec/lib/data_hygiene/govspeak_compare_spec.rb +++ b/spec/lib/data_hygiene/govspeak_compare_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe DataHygiene::GovspeakCompare do - let(:content_item) { FactoryGirl.create(:content_item, details: details) } + let(:edition) { FactoryGirl.create(:edition, details: details) } let(:details) do { body: [ { content_type: "text/html", content: body_html }, @@ -20,7 +20,7 @@ let(:other_govspeak) { body_govspeak } describe '.published_html' do - subject { described_class.new(content_item).published_html } + subject { described_class.new(edition).published_html } context "when details is an empty hash" do let(:details) { {} } @@ -53,7 +53,7 @@ end describe '.generated_html' do - subject { described_class.new(content_item).generated_html } + subject { described_class.new(edition).generated_html } context "when details is an empty hash" do let(:details) { {} } @@ -79,7 +79,7 @@ end describe '.diffs' do - subject { described_class.new(content_item).diffs } + subject { described_class.new(edition).diffs } context "when published_html is the same as generated_html" do let(:body_html) { "

Hello World

\n" } @@ -141,7 +141,7 @@ end describe 'same_html?' do - subject { described_class.new(content_item).same_html? } + subject { described_class.new(edition).same_html? } context "when govspeak will render to the same HTML" do let(:body_html) { "

Foo

\n" } @@ -164,7 +164,7 @@ end describe 'pretty_much_same_html?' do - subject { described_class.new(content_item).pretty_much_same_html? } + subject { described_class.new(edition).pretty_much_same_html? } context "when govspeak will render to the same HTML" do let(:body_html) { "

Foo

\n" } diff --git a/spec/lib/events/s3_exporter_spec.rb b/spec/lib/events/s3_exporter_spec.rb index d949a3e8c..3a5b38f41 100644 --- a/spec/lib/events/s3_exporter_spec.rb +++ b/spec/lib/events/s3_exporter_spec.rb @@ -10,8 +10,7 @@ let(:resource_double) { instance_double("Aws::S3::Resource", bucket: bucket_double) } let(:bucket_double) { instance_double("Aws::S3::Bucket", object: object_double) } let(:object_double) do - instance_double( - "Aws::S3::Object", + instance_double("Aws::S3::Object", exists?: object_exists?, put: Aws::S3::Types::PutObjectOutput.new ) @@ -80,29 +79,25 @@ def build_csv(expected_events) context "when there are items to export" do let(:created_before) { theresa_may_appointed } let!(:theresa_may_event) do - FactoryGirl.create( - :event, + FactoryGirl.create(:event, title: "Theresa May becomes Prime Minister", created_at: theresa_may_appointed, ) end let!(:david_cameron_event) do - FactoryGirl.create( - :event, + FactoryGirl.create(:event, title: "David Cameron becomes Prime Minister", created_at: david_cameron_appointed, ) end let!(:gordon_brown_event) do - FactoryGirl.create( - :event, + FactoryGirl.create(:event, title: "Gordon Brown becomes Prime Minister", created_at: gordon_brown_appointed, ) end let!(:tony_blair_event) do - FactoryGirl.create( - :event, + FactoryGirl.create(:event, title: "Tony Blair becomes Prime Minister", created_at: tony_blair_appointed, ) diff --git a/spec/lib/queue_publisher_spec.rb b/spec/lib/queue_publisher_spec.rb index 6132f235e..c73a36f82 100644 --- a/spec/lib/queue_publisher_spec.rb +++ b/spec/lib/queue_publisher_spec.rb @@ -69,7 +69,7 @@ queue_publisher.send_message(content_item) end - context "content item using string keys" do + context "edition using string keys" do let(:content_item) { super().stringify_keys } it "correctly calculates routing key" do diff --git a/spec/lib/requeue_content_spec.rb b/spec/lib/requeue_content_spec.rb index ad95a8d07..215eb0c63 100644 --- a/spec/lib/requeue_content_spec.rb +++ b/spec/lib/requeue_content_spec.rb @@ -2,15 +2,13 @@ RSpec.describe RequeueContent do before do - ContentItem.destroy_all + FactoryGirl.create(:live_edition, base_path: '/ci1') + FactoryGirl.create(:live_edition, base_path: '/ci2') + FactoryGirl.create(:live_edition, base_path: '/ci3') end - let!(:content_item1) { FactoryGirl.create(:live_content_item, base_path: '/ci1') } - let!(:content_item2) { FactoryGirl.create(:live_content_item, base_path: '/ci2') } - let!(:content_item3) { FactoryGirl.create(:live_content_item, base_path: '/ci3') } - describe "#call" do - it "by default, it republishes all content items" do + it "by default, it republishes all editions" do expect(PublishingAPI.service(:queue_publisher)).to receive(:send_message).exactly(3).times RequeueContent.new.call end diff --git a/spec/lib/tasks/data_sanitizer_spec.rb b/spec/lib/tasks/data_sanitizer_spec.rb index c2d303544..b53e6aa1d 100644 --- a/spec/lib/tasks/data_sanitizer_spec.rb +++ b/spec/lib/tasks/data_sanitizer_spec.rb @@ -2,24 +2,17 @@ RSpec.describe Tasks::DataSanitizer do let!(:non_limited_draft) do - FactoryGirl.create( - :draft_content_item, - base_path: "/non-limited-draft", - ) + FactoryGirl.create(:draft_edition, base_path: "/non-limited-draft") end let!(:limited_draft) do - FactoryGirl.create( - :access_limited_draft_content_item, + FactoryGirl.create(:access_limited_draft_edition, base_path: "/limited-draft", ) end - let!(:live_content_item) do - FactoryGirl.create( - :live_content_item, - base_path: "/live-item", - ) + let!(:live_edition) do + FactoryGirl.create(:live_edition, base_path: "/live-item") end let(:stdout) { double(:stdout, puts: nil) } @@ -31,9 +24,9 @@ it "deletes all access limited drafts" do Tasks::DataSanitizer.delete_access_limited(stdout) - expect(ContentItem.exists?(limited_draft.id)).to eq(false) - expect(ContentItem.exists?(non_limited_draft.id)).to eq(true) - expect(ContentItem.exists?(live_content_item.id)).to eq(true) + expect(Edition.exists?(limited_draft.id)).to eq(false) + expect(Edition.exists?(non_limited_draft.id)).to eq(true) + expect(Edition.exists?(live_edition.id)).to eq(true) end it "deletes access limited drafts from the draft content store" do @@ -46,9 +39,9 @@ it "removes the limited draft" do expect { Tasks::DataSanitizer.delete_access_limited(stdout) - }.to change(ContentItem, :count).by(-1) + }.to change(Edition, :count).by(-1) - expect(ContentItem.exists?(limited_draft.id)).to eq(false) + expect(Edition.exists?(limited_draft.id)).to eq(false) expect(AccessLimit.count).to be_zero end end diff --git a/spec/lib/tasks/version_validator_spec.rb b/spec/lib/tasks/version_validator_spec.rb index 03d3202f4..ec42760ed 100644 --- a/spec/lib/tasks/version_validator_spec.rb +++ b/spec/lib/tasks/version_validator_spec.rb @@ -2,20 +2,17 @@ RSpec.describe Tasks::VersionValidator do let(:content_id) { SecureRandom.uuid } + let(:document) { FactoryGirl.create(:document, content_id: content_id) } before do - FactoryGirl.create( - :superseded_content_item, - content_id: content_id, + FactoryGirl.create(:superseded_edition, + document: document, user_facing_version: 1, - locale: "en" ) - FactoryGirl.create( - :live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, user_facing_version: 2, - locale: "en" ) end @@ -29,22 +26,25 @@ context "when two items of the same content_id have a gap between versions" do before do - item = ContentItem.last + item = Edition.last item.user_facing_version = 3 item.save!(validate: false) end - it "outputs that the content item has an invalid version sequence" do + it "outputs that the edition has an invalid version sequence" do expect { subject.validate }.to output(/Invalid version sequence for #{content_id}/).to_stdout end end - context "when content items have the same version but different locale" do + context "when editions have the same version but different locale" do before do - item = ContentItem.last - item.locale = 'fr' + item = Edition.last + item.document = FactoryGirl.create(:document, + content_id: item.document.content_id, + locale: "fr" + ) item.user_facing_version = 1 item.save!(validate: false) end @@ -58,12 +58,12 @@ context "when the version sequence does not begin at zero" do before do - item = ContentItem.first + item = Edition.first item.user_facing_version = 3 item.save!(validate: false) end - it "outputs that the content item has an invalid version sequence" do + it "outputs that the edition has an invalid version sequence" do expect { subject.validate }.to output(/Invalid version sequence for #{content_id}/).to_stdout diff --git a/spec/models/action_spec.rb b/spec/models/action_spec.rb index f8d9ca15e..33d55354e 100644 --- a/spec/models/action_spec.rb +++ b/spec/models/action_spec.rb @@ -1,27 +1,27 @@ require "rails_helper" RSpec.describe Action do - describe "Content Item and Link Set presence" do - let(:content_item) { nil } + describe "edition and link set presence" do + let(:edition) { nil } let(:link_set) { nil } - subject { FactoryGirl.build(:action, content_item: content_item, link_set: link_set) } + subject { FactoryGirl.build(:action, edition: edition, link_set: link_set) } - context "No Content Item or Link Set" do + context "no edition or link set" do it { is_expected.to be_valid } end - context "Content Item and no Link Set" do - let(:content_item) { FactoryGirl.create(:content_item) } + context "edition and no link set" do + let(:edition) { FactoryGirl.create(:edition) } it { is_expected.to be_valid } end - context "Content Item and no Link Set" do + context "link set and no edition" do let(:link_set) { FactoryGirl.create(:link_set) } it { is_expected.to be_valid } end - context "Content Item and Link Set" do - let(:content_item) { FactoryGirl.create(:content_item) } + context "edition and link set" do + let(:edition) { FactoryGirl.create(:edition) } let(:link_set) { FactoryGirl.create(:link_set) } it { is_expected.not_to be_valid } end diff --git a/spec/models/change_note_spec.rb b/spec/models/change_note_spec.rb index dff2d15b4..42380cd06 100644 --- a/spec/models/change_note_spec.rb +++ b/spec/models/change_note_spec.rb @@ -5,17 +5,16 @@ let(:details) { {} } let(:payload_change_note) { nil } let(:update_type) { "major" } - let(:content_item) do - FactoryGirl.create( - :content_item, + let(:edition) do + FactoryGirl.create(:edition, update_type: update_type, details: details, change_note: nil, ) end - describe ".create_from_content_item" do - subject { described_class.create_from_content_item(payload, content_item) } + describe ".create_from_edition" do + subject { described_class.create_from_edition(payload, edition) } context "update_type is not major" do let(:update_type) { "minor" } @@ -35,17 +34,17 @@ end end - context "change note is entered for an existing content item" do + context "change note is entered for an existing edition" do it "updates the change note rather than creating a new one" do subject expect { - described_class.create_from_content_item(payload, content_item) + described_class.create_from_edition(payload, edition) }.to_not change { ChangeNote.count } end end end - context "content item has change_note entry in details hash" do + context "edition has change_note entry in details hash" do let(:details) { { change_note: "Marvellous" }.stringify_keys } it "populates change note from details hash" do expect { subject }.to change { ChangeNote.count }.by(1) @@ -53,21 +52,21 @@ end end - context "content item has change_note entry in details hash" do + context "edition has change_note entry in details hash" do let(:details) { { change_history: [] } } it "populates change note from details hash" do expect { subject }.to_not change { ChangeNote.count } end end - context "content item has change_note entry in details hash" do + context "edition has change_note entry in details hash" do let(:details) { { change_history: nil } } it "populates change note from details hash" do expect { subject }.to_not change { ChangeNote.count } end end - context "content item has change_history entry in details hash" do + context "edition has change_history entry in details hash" do let(:details) do { change_history: [ { public_timestamp: 3.day.ago.to_s, note: "note 3" }, diff --git a/spec/models/document_spec.rb b/spec/models/document_spec.rb new file mode 100644 index 000000000..d0c25503f --- /dev/null +++ b/spec/models/document_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Document do + subject { FactoryGirl.build(:document) } + + describe "validations" do + it "is valid for the default factory" do + expect(subject).to be_valid + end + + it "requires a content_id" do + subject.content_id = nil + expect(subject).to be_invalid + end + + context "content_id" do + it "accepts a UUID" do + subject.content_id = "a7c48dac-f1c6-45a8-b5c1-5c407d45826f" + expect(subject).to be_valid + end + + it "does not accept an arbitrary string" do + subject.content_id = "bacon" + expect(subject).not_to be_valid + end + + it "does not accept an empty string" do + subject.content_id = "" + expect(subject).not_to be_valid + end + end + end +end diff --git a/spec/models/content_item_spec.rb b/spec/models/edition_spec.rb similarity index 65% rename from spec/models/content_item_spec.rb rename to spec/models/edition_spec.rb index f55b14b77..127e7ddc9 100644 --- a/spec/models/content_item_spec.rb +++ b/spec/models/edition_spec.rb @@ -1,14 +1,14 @@ require "rails_helper" -RSpec.describe ContentItem do - subject { FactoryGirl.build(:content_item) } +RSpec.describe Edition do + subject { FactoryGirl.build(:edition) } describe ".renderable_content" do - let!(:guide) { FactoryGirl.create(:content_item, schema_name: "guide") } - let!(:redirect) { FactoryGirl.create(:redirect_content_item) } - let!(:gone) { FactoryGirl.create(:gone_content_item) } + let!(:guide) { FactoryGirl.create(:edition, schema_name: "guide") } + let!(:redirect) { FactoryGirl.create(:redirect_edition) } + let!(:gone) { FactoryGirl.create(:gone_edition) } - it "returns content items that do not have a schema_name of 'redirect' or 'gone'" do + it "returns editions that do not have a schema_name of 'redirect' or 'gone'" do expect(described_class.renderable_content).to eq [guide] end end @@ -18,8 +18,8 @@ expect(subject).to be_valid end - it "requires a content_id" do - subject.content_id = nil + it "requires a document" do + subject.document = nil expect(subject).to be_invalid end @@ -33,7 +33,7 @@ expect(subject).to be_invalid end - context "when the content item is 'renderable'" do + context "when the edition is 'renderable'" do before do subject.document_type = "guide" end @@ -67,8 +67,8 @@ end end - context "when the content item is not 'renderable'" do - subject { FactoryGirl.build(:redirect_content_item) } + context "when the edition is not 'renderable'" do + subject { FactoryGirl.build(:redirect_edition) } it "does not require a title" do subject.title = "" @@ -81,8 +81,8 @@ end end - context "when the content item is optionally 'renderable'" do - subject { FactoryGirl.build(:content_item, document_type: "contact") } + context "when the edition is optionally 'renderable'" do + subject { FactoryGirl.build(:edition, document_type: "contact") } it "does not require a rendering_app" do subject.rendering_app = nil @@ -90,23 +90,6 @@ end end - context "content_id" do - it "accepts a UUID" do - subject.content_id = "a7c48dac-f1c6-45a8-b5c1-5c407d45826f" - expect(subject).to be_valid - end - - it "does not accept an arbitrary string" do - subject.content_id = "bacon" - expect(subject).not_to be_valid - end - - it "does not accept an empty string" do - subject.content_id = "" - expect(subject).not_to be_valid - end - end - context "base_path" do it "should be an absolute path" do subject.base_path = 'invalid//absolute/path/' @@ -115,18 +98,18 @@ end end - context "when another content item has the same base path" do - before { FactoryGirl.create(:draft_content_item, base_path: "/foo") } + context "when another edition has the same base path" do + before { FactoryGirl.create(:draft_edition, base_path: "/foo") } - let(:content_item) do - FactoryGirl.build(:content_item, base_path: "/foo", state: "draft") + let(:edition) do + FactoryGirl.build(:edition, base_path: "/foo", state: "draft") end - subject { content_item } + subject { edition } it { is_expected.to be_invalid } context "and the state is different" do - before { content_item.state = "published" } + before { edition.state = "published" } it { is_expected.to be_valid } end @@ -156,15 +139,14 @@ end end - context "when the state conflicts with another instance of this content item" do - subject { content_item } - let(:existing_content_item) do - FactoryGirl.create(:draft_content_item, user_facing_version: 2) + context "when the state conflicts with another instance of this edition" do + subject { edition } + let(:existing_edition) do + FactoryGirl.create(:draft_edition, user_facing_version: 2) end - let(:content_item) do - FactoryGirl.build( - :draft_content_item, - content_id: existing_content_item.content_id, + let(:edition) do + FactoryGirl.build(:draft_edition, + document: existing_edition.document, user_facing_version: 1 ) end @@ -172,47 +154,33 @@ it { is_expected.to be_invalid } context "and the states are different" do - before { content_item.state = "published" } - - it { is_expected.to be_valid } - end - - context "and the locales are different" do - before { content_item.locale = "fr" } + before { edition.state = "published" } it { is_expected.to be_valid } end end - context "when the user facing version conflicts with another instance of this content item" do - subject { content_item } - let(:existing_content_item) { FactoryGirl.create(:draft_content_item) } - let(:content_item) do - FactoryGirl.build( - :draft_content_item, - content_id: existing_content_item.content_id, + context "when the user facing version conflicts with another instance of this edition" do + subject { edition } + let(:existing_edition) { FactoryGirl.create(:draft_edition) } + let(:edition) do + FactoryGirl.build(:draft_edition, + document: existing_edition.document, user_facing_version: 1 ) end it { is_expected.to be_invalid } - - context "and the locales are different" do - before { content_item.locale = "fr" } - - it { is_expected.to be_valid } - end end context "when the draft user_facing_version is ahead of the live one" do - subject { content_item } - let(:existing_content_item) do - FactoryGirl.create(:live_content_item, user_facing_version: 1) + subject { edition } + let(:existing_edition) do + FactoryGirl.create(:live_edition, user_facing_version: 1) end - let(:content_item) do - FactoryGirl.build( - :draft_content_item, - content_id: existing_content_item.content_id, + let(:edition) do + FactoryGirl.build(:draft_edition, + document: existing_edition.document, user_facing_version: 2 ) end @@ -221,14 +189,13 @@ end context "when the draft user_facing_version is behind the live one" do - subject { content_item } - let(:existing_content_item) do - FactoryGirl.create(:draft_content_item, user_facing_version: 1) + subject { edition } + let(:existing_edition) do + FactoryGirl.create(:draft_edition, user_facing_version: 1) end - let(:content_item) do - FactoryGirl.build( - :live_content_item, - content_id: existing_content_item.content_id, + let(:edition) do + FactoryGirl.build(:live_edition, + document: existing_edition.document, user_facing_version: 2 ) end @@ -237,14 +204,13 @@ end context "when the live user_facing_version is ahead of the draft one" do - subject { content_item } - let(:existing_content_item) do - FactoryGirl.create(:live_content_item, user_facing_version: 2) + subject { edition } + let(:existing_edition) do + FactoryGirl.create(:live_edition, user_facing_version: 2) end - let(:content_item) do - FactoryGirl.build( - :draft_content_item, - content_id: existing_content_item.content_id, + let(:edition) do + FactoryGirl.build(:draft_edition, + document: existing_edition.document, user_facing_version: 1 ) end @@ -253,31 +219,31 @@ end context "when user_facing_version is incremented" do - subject { content_item } - let(:content_item) { FactoryGirl.create(:content_item) } + subject { edition } + let(:edition) { FactoryGirl.create(:edition) } - before { content_item.user_facing_version += 1 } + before { edition.user_facing_version += 1 } it { is_expected.to be_valid } end context "when user_facing_version is decremented" do - subject { content_item } - let(:content_item) { FactoryGirl.create(:content_item) } + subject { edition } + let(:edition) { FactoryGirl.create(:edition) } - before { content_item.user_facing_version -= 1 } + before { edition.user_facing_version -= 1 } it { is_expected.to be_invalid } end describe "routes and redirects" do - subject { content_item } - let(:content_item) { FactoryGirl.build(:content_item, base_path: "/vat-rates") } + subject { edition } + let(:edition) { FactoryGirl.build(:edition, base_path: "/vat-rates") } it_behaves_like RoutesAndRedirectsValidator end end context "EMPTY_BASE_PATH_FORMATS" do it "defines formats not requiring a base_path attibute" do - expect(ContentItem::EMPTY_BASE_PATH_FORMATS).to eq(%w(contact government)) + expect(Edition::EMPTY_BASE_PATH_FORMATS).to eq(%w(contact government)) end end @@ -300,7 +266,7 @@ describe "#details_for_govspeak_conversion" do subject do - FactoryGirl.build(:content_item, details: details) + FactoryGirl.build(:edition, details: details) .details_for_govspeak_conversion end @@ -370,7 +336,7 @@ end context "#unpublish" do - subject { FactoryGirl.build(:live_content_item) } + subject { FactoryGirl.build(:live_edition) } it "changes the content_store to nil when type substitute" do expect { subject.unpublish(type: "substitute") }.to change { subject.content_store }.from("live").to(nil) diff --git a/spec/models/link_spec.rb b/spec/models/link_spec.rb index 77f5a735a..456647721 100644 --- a/spec/models/link_spec.rb +++ b/spec/models/link_spec.rb @@ -41,15 +41,15 @@ end end - describe ".filter_content_items" do + describe ".filter_editions" do let(:scope) { double(:scope) } - it "modifies a scope to filter linked content items" do + it "modifies a scope to filter linked editions" do expect(scope).to receive(:joins).with(anything).and_return(scope) expect(scope).to receive(:where) .with("links.link_type": "organisations", "links.target_content_id": "12345") - described_class.filter_content_items(scope, "organisations" => "12345") + described_class.filter_editions(scope, "organisations" => "12345") end end end diff --git a/spec/models/lock_version_spec.rb b/spec/models/lock_version_spec.rb index 84ff11e9b..72c252057 100644 --- a/spec/models/lock_version_spec.rb +++ b/spec/models/lock_version_spec.rb @@ -2,17 +2,27 @@ RSpec.describe LockVersion do subject do - content_item = FactoryGirl.create(:content_item) - FactoryGirl.build(:lock_version, target: content_item) + edition = FactoryGirl.create(:edition) + FactoryGirl.build(:lock_version, target: edition) end it "starts version numbers at 0" do - content_item = FactoryGirl.create(:content_item) - lock_version = LockVersion.create!(target: content_item) + edition = FactoryGirl.create(:edition) + lock_version = LockVersion.create!(target: edition) expect(lock_version.number).to be_zero expect(lock_version).to be_valid end + it "works with a ContentItem target_type" do + edition = FactoryGirl.create(:edition) + lock_version = LockVersion.create!(target: edition) + lock_version.update_attributes!(target_type: "ContentItem") + + expect(lock_version.number).to be_zero + expect(lock_version).to be_valid + expect(lock_version.target).to_not be_nil + end + describe "#conflicts_with?(previous_version_number)" do before do subject.number = 2 diff --git a/spec/models/path_reservation_spec.rb b/spec/models/path_reservation_spec.rb index 293cac478..85a7f259d 100644 --- a/spec/models/path_reservation_spec.rb +++ b/spec/models/path_reservation_spec.rb @@ -46,8 +46,7 @@ describe ".reserve_base_path!(base_path, publishing_app)" do context "when the path reservation already exists" do before do - FactoryGirl.create( - :path_reservation, + FactoryGirl.create(:path_reservation, base_path: "/vat-rates", publishing_app: "something-else", ) diff --git a/spec/models/state_spec.rb b/spec/models/state_spec.rb index 9d382145b..69ce1085f 100644 --- a/spec/models/state_spec.rb +++ b/spec/models/state_spec.rb @@ -10,7 +10,7 @@ end describe ".unpublish" do - let(:live_item) { FactoryGirl.create(:live_content_item) } + let(:live_item) { FactoryGirl.create(:live_edition) } it "changes the state name to 'unpublished'" do expect { @@ -29,7 +29,7 @@ unpublishing = Unpublishing.last - expect(unpublishing.content_item).to eq(live_item) + expect(unpublishing.edition).to eq(live_item) expect(unpublishing.type).to eq("gone") expect(unpublishing.explanation).to eq("A test explanation") expect(unpublishing.alternative_path).to eq("/some-path") diff --git a/spec/models/symbolize_json_spec.rb b/spec/models/symbolize_json_spec.rb index 44f8c4d89..f49e78dff 100644 --- a/spec/models/symbolize_json_spec.rb +++ b/spec/models/symbolize_json_spec.rb @@ -1,18 +1,14 @@ require "rails_helper" RSpec.describe SymbolizeJSON do - subject { FactoryGirl.build(:draft_content_item) } + subject { FactoryGirl.build(:draft_edition) } it "doesn't affect non-json columns" do - content_id = SecureRandom.uuid - - subject.content_id = content_id subject.public_updated_at = Date.new(2000, 1, 1) subject.save! subject.reload - expect(subject.content_id).to eq(content_id) expect(subject.public_updated_at).to eq(Date.new(2000, 1, 1)) end diff --git a/spec/models/unpublishing_spec.rb b/spec/models/unpublishing_spec.rb index 7c976ee1d..ba6e47a24 100644 --- a/spec/models/unpublishing_spec.rb +++ b/spec/models/unpublishing_spec.rb @@ -59,14 +59,14 @@ context "when alternative_path is equal to base_path" do let(:base_path) { "/new-path" } - let(:content_item) do - FactoryGirl.create(:content_item, + let(:edition) do + FactoryGirl.create(:edition, base_path: base_path, ) end it "is invalid" do - subject.content_item = content_item + subject.edition = edition subject.type = "redirect" subject.alternative_path = base_path @@ -86,21 +86,21 @@ end describe ".is_subtitute?" do - subject { described_class.is_substitute?(content_item) } + subject { described_class.is_substitute?(edition) } context "when unpublished with type 'substitute'" do - let(:content_item) { FactoryGirl.create(:substitute_unpublished_content_item) } + let(:edition) { FactoryGirl.create(:substitute_unpublished_edition) } it { is_expected.to be true } end context "when unpublished with type 'gone'" do - let(:content_item) { FactoryGirl.create(:gone_unpublished_content_item) } + let(:edition) { FactoryGirl.create(:gone_unpublished_edition) } it { is_expected.to be false } end - context "when content item is published" do - let(:content_item) { FactoryGirl.create(:live_content_item) } + context "when edition is published" do + let(:edition) { FactoryGirl.create(:live_edition) } it { is_expected.to be false } end - context "when there isn't a content item" do - let(:content_item) { nil } + context "when there isn't an edition" do + let(:edition) { nil } it { is_expected.to be false } end end diff --git a/spec/pacts/content_store/put_endpoint_spec.rb b/spec/pacts/content_store/put_endpoint_spec.rb index fb3d94193..6b0d1097c 100644 --- a/spec/pacts/content_store/put_endpoint_spec.rb +++ b/spec/pacts/content_store/put_endpoint_spec.rb @@ -4,10 +4,9 @@ include Pact::Consumer::RSpec include RequestHelpers::Mocks - let!(:content_item) do - FactoryGirl.create( - :live_content_item, - content_id: content_id, + let!(:edition) do + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: "/vat-rates" ) end @@ -19,7 +18,7 @@ let(:body) { Presenters::ContentStorePresenter.present( Presenters::DownstreamPresenter.new( - Queries::GetWebContentItems.find(content_item.id), + Queries::GetWebContentItems.find(edition.id), state_fallback_order: [:published] ), event.id diff --git a/spec/presenters/change_history_presenter_spec.rb b/spec/presenters/change_history_presenter_spec.rb index fa129cd01..4e8de8d3b 100644 --- a/spec/presenters/change_history_presenter_spec.rb +++ b/spec/presenters/change_history_presenter_spec.rb @@ -2,15 +2,16 @@ RSpec.describe Presenters::ChangeHistoryPresenter do let(:content_id) { SecureRandom.uuid } - let(:content_item) do - FactoryGirl.create( - :content_item, + let(:document) { FactoryGirl.create(:document, content_id: content_id) } + let(:edition) do + FactoryGirl.create(:edition, + document: document, details: details.deep_stringify_keys, - content_id: content_id, ) end + let(:web_content_item) { Queries::GetWebContentItems.find(edition.id) } let(:details) { {} } - subject { described_class.new(content_item).change_history } + subject { described_class.new(web_content_item).change_history } describe "#change_history" do context "details hash includes content_history" do @@ -29,8 +30,8 @@ before do 2.times do |i| ChangeNote.create( - content_item: content_item, - content_id: content_item.content_id, + edition: edition, + content_id: content_id, note: i.to_s, public_timestamp: Time.now.utc ) @@ -44,8 +45,8 @@ it "orders change notes by public_timestamp (ascending)" do [1, 3, 2].to_a.each do |i| ChangeNote.create( - content_item: content_item, - content_id: content_item.content_id, + edition: edition, + content_id: content_id, note: i.to_s, public_timestamp: i.days.ago ) @@ -53,38 +54,38 @@ expect(subject.map { |item| item[:note] }).to eq %w(3 2 1) end - context "multiple content items for a single content id" do + context "multiple editions for a single content id" do let(:item1) do - FactoryGirl.create( - :superseded_content_item, + FactoryGirl.create(:superseded_edition, + document: document, details: details, - content_id: content_id, user_facing_version: 1, ) end + let(:web_content_item1) { Queries::GetWebContentItems.find(item1.id) } let(:item2) do - FactoryGirl.create( - :live_content_item, + FactoryGirl.create(:live_edition, + document: document, details: details, - content_id: content_id, user_facing_version: 2, ) end + let(:web_content_item2) { Queries::GetWebContentItems.find(item2.id) } before do - ChangeNote.create(content_item: item1, content_id: content_id) - ChangeNote.create(content_item: item2, content_id: content_id) + ChangeNote.create(edition: item1, content_id: content_id) + ChangeNote.create(edition: item2, content_id: content_id) ChangeNote.create(content_id: content_id) end - context "reviewing latest version of a content item" do + context "reviewing latest version of a edition" do it "constructs content history from all change notes for content id" do - expect(described_class.new(item2).change_history.count).to eq 3 + expect(described_class.new(web_content_item2).change_history.count).to eq 3 end end - context "reviewing older version of a content item" do + context "reviewing older version of a edition" do it "doesn't include change notes corresponding to newer versions" do - expect(described_class.new(item1).change_history.count).to eq 2 + expect(described_class.new(web_content_item1).change_history.count).to eq 2 end end end diff --git a/spec/presenters/content_store_presenter_spec.rb b/spec/presenters/content_store_presenter_spec.rb index c3fe86c6b..722a841a5 100644 --- a/spec/presenters/content_store_presenter_spec.rb +++ b/spec/presenters/content_store_presenter_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Presenters::ContentStorePresenter do let(:downstream_presenter) { - web_content_item = Queries::GetWebContentItems.find(FactoryGirl.create(:live_content_item).id) + web_content_item = Queries::GetWebContentItems.find(FactoryGirl.create(:live_edition).id) Presenters::DownstreamPresenter.new(web_content_item, nil, state_fallback_order: [:published]) } let(:event) { double(:event, id: 123) } diff --git a/spec/presenters/debug_presenter_spec.rb b/spec/presenters/debug_presenter_spec.rb new file mode 100644 index 000000000..a579ee7cf --- /dev/null +++ b/spec/presenters/debug_presenter_spec.rb @@ -0,0 +1,87 @@ +require "rails_helper" + +RSpec.describe Presenters::DebugPresenter do + let(:document) { FactoryGirl.create(:document) } + + let!(:edition) do + FactoryGirl.create(:draft_edition, + document: document, + user_facing_version: 3) + end + + let!(:link_set) { FactoryGirl.create(:link_set, content_id: document.content_id) } + + subject do + described_class.new(document.content_id) + end + + describe ".editions" do + it "has one entry" do + expect(subject.editions.length).to eq(1) + end + end + + describe ".user_facing_versions" do + it "has one entry" do + expect(subject.user_facing_versions.length).to eq(1) + end + + it "matches" do + expect(subject.user_facing_versions[0]).to eq(3) + end + end + + describe ".latest_editions" do + it "has one entry" do + expect(subject.latest_editions.length).to eq(1) + end + end + + describe ".latest_state_with_locale" do + it "matches" do + expect(subject.latest_state_with_locale[0]).to eq(%w(en draft)) + end + end + + describe ".web_content_item" do + it "matches" do + expect(subject.web_content_item.base_path).to match("/vat-rates-") + end + end + + describe ".title" do + it "matches" do + expect(subject.title).to eq("VAT rates") + end + end + + describe ".web_url" do + it "matches" do + expect(subject.web_url).to match("/vat-rates-") + end + end + + describe ".api_url" do + it "matches" do + expect(subject.api_url).to match("api/content/vat-rates-") + end + end + + describe ".link_set" do + it "is not nil" do + expect(subject.link_set).to_not be_nil + end + end + + describe ".expanded_links" do + it "has four entries" do + expect(subject.expanded_links.length).to eq(3) + end + end + + describe ".states" do + it "has four entries" do + expect(subject.states.length).to eq(2) + end + end +end diff --git a/spec/presenters/details_presenter_spec.rb b/spec/presenters/details_presenter_spec.rb index ed08c8116..d5274fcb5 100644 --- a/spec/presenters/details_presenter_spec.rb +++ b/spec/presenters/details_presenter_spec.rb @@ -6,33 +6,31 @@ instance_double(Presenters::ChangeHistoryPresenter, change_history: []) end subject do - described_class.new( - content_item_details, change_history_presenter - ).details + described_class.new(edition_details, change_history_presenter).details end context "when we're passed details without a body" do - let(:content_item_details) { {} } + let(:edition_details) { {} } it "matches original details" do - is_expected.to match(content_item_details) + is_expected.to match(edition_details) end end context "when we're passed a body which isn't enumerable" do - let(:content_item_details) do + let(:edition_details) do { body: "Something about VAT" } end it "matches original details" do - is_expected.to match(content_item_details) + is_expected.to match(edition_details) end end context "when we're passed details with govspeak and HTML" do - let(:content_item_details) do + let(:edition_details) do { body: [ { content_type: "text/html", content: "html" }, @@ -42,12 +40,12 @@ end it "matches original details" do - is_expected.to match(content_item_details) + is_expected.to match(edition_details) end end context "when we're passed govspeak without HTML" do - let(:content_item_details) do + let(:edition_details) do { body: [ { content_type: "text/govspeak", content: "**hello**" } @@ -68,7 +66,7 @@ end context "when we're passed multiple govspeak fields" do - let(:content_item_details) do + let(:edition_details) do { body: [ { content_type: "text/govspeak", content: "**hello**" } @@ -97,7 +95,7 @@ end context "when we're passed hashes rather than arrays" do - let(:content_item_details) do + let(:edition_details) do { body: { content_type: "text/govspeak", content: "**hello**" }, } @@ -116,17 +114,17 @@ end context "when we're passed an image hash" do - let(:content_item_details) do + let(:edition_details) do { image: { content_type: "image/png", content: "some content" } } end it "doesn't wrap the hash in an array" do - expect(subject).to eq content_item_details + expect(subject).to eq edition_details end end context "value contains nested array" do - let(:content_item_details) { { other: %w(an array of strings) } } + let(:edition_details) { { other: %w(an array of strings) } } it "doesn't try to convert to govspeak" do expect { subject }.to_not raise_error end diff --git a/spec/presenters/downstream_presenter_spec.rb b/spec/presenters/downstream_presenter_spec.rb index fd62adb34..387f2a2f7 100644 --- a/spec/presenters/downstream_presenter_spec.rb +++ b/spec/presenters/downstream_presenter_spec.rb @@ -1,12 +1,12 @@ require 'rails_helper' RSpec.describe Presenters::DownstreamPresenter do - def web_content_item_for(content_item) - Queries::GetWebContentItems.(content_item.id).first + def web_content_item_for(edition) + Queries::GetWebContentItems.(edition.id).first end let(:state_fallback_order) { [] } - let(:web_content_item) { web_content_item_for(content_item) } + let(:web_content_item) { web_content_item_for(edition) } let(:change_history) { { note: "Note", public_timestamp: 1.day.ago.to_s } } let(:details) { { body: "

Text

\n", change_history: [change_history], } } @@ -17,7 +17,7 @@ def web_content_item_for(content_item) let(:expected) { { - content_id: content_item.content_id, + content_id: edition.document.content_id, base_path: base_path, analytics_identifier: "GDS01", description: "VAT rates for goods and services", @@ -40,45 +40,42 @@ def web_content_item_for(content_item) } } - context "for a live content item" do - let(:content_item) do - FactoryGirl.create( - :live_content_item, + context "for a live edition" do + let(:edition) do + FactoryGirl.create(:live_edition, base_path: base_path, details: details) end - let!(:link_set) { FactoryGirl.create(:link_set, content_id: content_item.content_id) } + let!(:link_set) { FactoryGirl.create(:link_set, content_id: edition.document.content_id) } it "presents the object graph for the content store" do expect(result).to eq(expected) end end - context "for a draft content item" do - let(:content_item) do - FactoryGirl.create( - :draft_content_item, + context "for a draft edition" do + let(:edition) do + FactoryGirl.create(:draft_edition, base_path: base_path, details: details) end - let!(:link_set) { FactoryGirl.create(:link_set, content_id: content_item.content_id) } + let!(:link_set) { FactoryGirl.create(:link_set, content_id: edition.document.content_id) } it "presents the object graph for the content store" do expect(result).to eq(expected) end end - context "for a withdrawn content item" do - let!(:content_item) do - FactoryGirl.create( - :withdrawn_unpublished_content_item, + context "for a withdrawn edition" do + let!(:edition) do + FactoryGirl.create(:withdrawn_unpublished_edition, base_path: base_path, details: details) end - let!(:link_set) { FactoryGirl.create(:link_set, content_id: content_item.content_id) } + let!(:link_set) { FactoryGirl.create(:link_set, content_id: edition.document.content_id) } it "merges in a withdrawal notice" do - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(result).to eq( expected.merge( @@ -91,9 +88,8 @@ def web_content_item_for(content_item) end context "with an overridden unpublished_at" do - let!(:content_item) do - FactoryGirl.create( - :withdrawn_unpublished_content_item, + let!(:edition) do + FactoryGirl.create(:withdrawn_unpublished_edition, base_path: base_path, details: details, unpublished_at: DateTime.new(2016, 9, 10, 4, 5, 6) @@ -101,7 +97,7 @@ def web_content_item_for(content_item) end it "merges in a withdrawal notice with the withdrawn_at set correctly" do - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(result).to eq( expected.merge( @@ -115,22 +111,22 @@ def web_content_item_for(content_item) end end - context "for a content item with dependencies" do - let(:a) { FactoryGirl.create(:content_item, base_path: "/a") } - let(:b) { FactoryGirl.create(:content_item, base_path: "/b") } + context "for a edition with dependencies" do + let(:a) { FactoryGirl.create(:edition, base_path: "/a") } + let(:b) { FactoryGirl.create(:edition, base_path: "/b") } before do - FactoryGirl.create(:link_set, content_id: a.content_id, links: [ - FactoryGirl.create(:link, link_type: "related", target_content_id: b.content_id) + FactoryGirl.create(:link_set, content_id: a.document.content_id, links: [ + FactoryGirl.create(:link, link_type: "related", target_content_id: b.document.content_id) ]) end - it "expands the links for the content item" do + it "expands the links for the edition" do result = described_class.present(web_content_item_for(a), state_fallback_order: [:draft]) expect(result[:expanded_links]).to eq( related: [{ - content_id: b.content_id, + content_id: b.document.content_id, api_path: "/api/content/b", base_path: "/b", title: "VAT rates", @@ -147,7 +143,7 @@ def web_content_item_for(content_item) analytics_identifier: "GDS01", api_path: "/api/content/a", base_path: "/a", - content_id: a.content_id, + content_id: a.document.content_id, description: "VAT rates for goods and services", schema_name: "guide", document_type: 'guide', @@ -160,15 +156,14 @@ def web_content_item_for(content_item) end end - context "for a content item with change notes" do - let(:content_item) do - FactoryGirl.create( - :draft_content_item, + context "for a edition with change notes" do + let(:edition) do + FactoryGirl.create(:draft_edition, base_path: base_path, details: details.slice(:body)) end before do - ChangeNote.create(change_history.merge(content_item: content_item, content_id: content_item.content_id)) + ChangeNote.create(change_history.merge(edition: edition, content_id: edition.document.content_id)) end it "constructs the change history" do @@ -177,8 +172,8 @@ def web_content_item_for(content_item) end describe "conditional attributes" do - let!(:content_item) { FactoryGirl.create(:live_content_item) } - let!(:link_set) { FactoryGirl.create(:link_set, content_id: content_item.content_id) } + let!(:edition) { FactoryGirl.create(:live_edition) } + let!(:link_set) { FactoryGirl.create(:link_set, content_id: edition.document.content_id) } context "when the link_set is not present" do before { link_set.destroy } @@ -189,7 +184,7 @@ def web_content_item_for(content_item) end context "when the public_updated_at is not present" do - let(:content_item) { FactoryGirl.create(:gone_draft_content_item) } + let(:edition) { FactoryGirl.create(:gone_draft_edition) } it "does not raise an error" do expect { result }.not_to raise_error @@ -199,11 +194,11 @@ def web_content_item_for(content_item) context "for an access-limited item" do let!(:access_limit) { - FactoryGirl.create(:access_limit, content_item: content_item) + FactoryGirl.create(:access_limit, edition: edition) } context "in draft" do - let(:content_item) { FactoryGirl.create(:draft_content_item) } + let(:edition) { FactoryGirl.create(:draft_edition) } it "populates the access_limited hash" do expect(result[:access_limited][:users].length).to eq(1) @@ -211,7 +206,7 @@ def web_content_item_for(content_item) end context "in live" do - let(:content_item) { FactoryGirl.create(:live_content_item) } + let(:edition) { FactoryGirl.create(:live_edition) } it "does not send an access_limited hash" do expect(result).not_to include(:access_limited) @@ -236,9 +231,8 @@ def web_content_item_for(content_item) } end - let(:content_item) do - FactoryGirl.create( - :live_content_item, + let(:edition) do + FactoryGirl.create(:live_edition, base_path: base_path, details: details, ) diff --git a/spec/presenters/message_queue_presenter_spec.rb b/spec/presenters/message_queue_presenter_spec.rb index 2ccefa083..da3d351b9 100644 --- a/spec/presenters/message_queue_presenter_spec.rb +++ b/spec/presenters/message_queue_presenter_spec.rb @@ -2,9 +2,9 @@ RSpec.describe Presenters::MessageQueuePresenter do let(:downstream_presenter) { - content_item = FactoryGirl.create(:live_content_item) - web_content_item = Queries::GetWebContentItems.find(content_item.id) - link_set = FactoryGirl.create(:link_set, content_id: content_item.content_id) + edition = FactoryGirl.create(:live_edition) + web_content_item = Queries::GetWebContentItems.find(edition.id) + link_set = FactoryGirl.create(:link_set, content_id: edition.document.content_id) FactoryGirl.create(:link, target_content_id: "d16216ce-7487-4bde-b817-ef68317fe3ab", link_set: link_set, link_type: 'taxons') Presenters::DownstreamPresenter.new(web_content_item, link_set, state_fallback_order: [:published]) } diff --git a/spec/presenters/queries/available_translations_spec.rb b/spec/presenters/queries/available_translations_spec.rb index b01d6887c..b5ed37e86 100644 --- a/spec/presenters/queries/available_translations_spec.rb +++ b/spec/presenters/queries/available_translations_spec.rb @@ -8,14 +8,12 @@ ).translations[:available_translations] } - def create_content_item(base_path, state = "published", locale = "en", version = 1) - FactoryGirl.create( - :content_item, - content_id: link_set.content_id, + def create_edition(base_path, state = "published", locale = "en", version = 1) + FactoryGirl.create(:edition, + document: Document.find_or_create_by(content_id: link_set.content_id, locale: locale), base_path: base_path, state: state, content_store: state == 'draft' ? 'draft' : 'live', - locale: locale, user_facing_version: version, ) end @@ -26,9 +24,9 @@ def create_content_item(base_path, state = "published", locale = "en", version = let(:state_fallback_order) { [:published] } before do - create_content_item("/a", "published") - create_content_item("/a.ar", "published", "ar") - create_content_item("/a.es", "published", "es") + create_edition("/a", "published") + create_edition("/a.ar", "published", "ar") + create_edition("/a.es", "published", "es") end it "returns all the items" do @@ -41,9 +39,9 @@ def create_content_item(base_path, state = "published", locale = "en", version = end context "with items in more than one state" do - let!(:en) { create_content_item("/a", "published") } - let!(:ar) { create_content_item("/a.ar", "draft", "ar") } - let!(:es) { create_content_item("/a.es", "published", "es") } + let!(:en) { create_edition("/a", "published") } + let!(:ar) { create_edition("/a.ar", "draft", "ar") } + let!(:es) { create_edition("/a.es", "published", "es") } context "with multiple states in the fallback order" do let(:state_fallback_order) { [:draft, :published] } @@ -58,7 +56,7 @@ def create_content_item(base_path, state = "published", locale = "en", version = it "takes the item in the first matching state" do es.update_attribute("title", "no habla español") - draft_es = create_content_item("/a.es", "draft", "es", 2) + draft_es = create_edition("/a.es", "draft", "es", 2) draft_es.update_attribute("title", "mais on parle français") expect(translations).to match_array([ a_hash_including(base_path: "/a", locale: "en"), diff --git a/spec/presenters/queries/content_item_presenter_spec.rb b/spec/presenters/queries/content_item_presenter_spec.rb index fe28c933a..3b1b07a9c 100644 --- a/spec/presenters/queries/content_item_presenter_spec.rb +++ b/spec/presenters/queries/content_item_presenter_spec.rb @@ -4,15 +4,20 @@ let(:content_id) { SecureRandom.uuid } let(:base_path) { "/vat-rates" } + let!(:document) { FactoryGirl.create(:document, content_id: content_id) } + let!(:fr_document) do + FactoryGirl.create(:document, content_id: content_id, locale: "fr") + end + describe "present" do - let!(:content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + let!(:edition) do + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path ) end - let(:result) { described_class.present(content_item) } + let(:result) { described_class.present(edition) } let(:payload) do { @@ -49,19 +54,19 @@ end end - it "presents content item attributes as a hash" do + it "presents edition attributes as a hash" do expect(result).to eq(payload) end - context "for a draft content item" do + context "for a draft edition" do it "has a publication state of draft" do expect(result.fetch("publication_state")).to eq("draft") end end - context "for a published content item" do + context "for a published edition" do before do - content_item.update_attributes!(state: 'published') + edition.update_attributes!(state: 'published') end it "has a publication state of published" do @@ -69,24 +74,24 @@ end end - context "when the content item exists in multiple locales" do + context "when the edition exists in multiple locales" do let!(:french_item) do - FactoryGirl.create(:draft_content_item, content_id: content_id, locale: "fr") + FactoryGirl.create(:draft_edition, document: fr_document) end it "presents the item with matching locale" do result = described_class.present(french_item) expect(result.fetch("locale")).to eq("fr") - result = described_class.present(content_item) + result = described_class.present(edition) expect(result.fetch("locale")).to eq("en") end end context "when a change note exists" do - let!(:content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + let!(:edition) do + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, update_type: "major" ) @@ -103,55 +108,50 @@ end describe "#present_many" do - let!(:content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, - ) + let!(:edition) do + FactoryGirl.create(:draft_edition, document: document) end context "when an array of fields is provided" do let(:fields) { %w(title phase publication_state) } it "returns the requested fields" do - content_items = ContentItem.where(content_id: content_id) + editions = Edition.with_document.where("documents.content_id": content_id) - results = described_class.present_many(content_items, fields: fields) + results = described_class.present_many(editions, fields: fields) expect(results.first.keys).to match_array(%w(title phase publication_state)) end end - context "when the content item exists in multiple locales" do + context "when the edition exists in multiple locales" do let!(:french_item) do - FactoryGirl.create(:content_item, content_id: content_id, locale: "fr") + FactoryGirl.create(:edition, document: fr_document) end - it "presents a content item for each locale" do - content_items = ContentItem.where(content_id: content_id) + it "presents a edition for each locale" do + editions = Edition.with_document.where("documents.content_id": content_id) - results = described_class.present_many(content_items) + results = described_class.present_many(editions) locales = results.map { |r| r.fetch("locale") } expect(locales).to match_array(%w(fr en)) end end - context "when there are other content items with that content_id" do + context "when there are other editions with that content_id" do before do - content_item.update_attributes(user_facing_version: 2) + edition.update_attributes(user_facing_version: 2) end let!(:published_item) do - FactoryGirl.create( - :live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, user_facing_version: 1, ) end - let(:content_items) { ContentItem.where(content_id: content_id) } - - it "returns a versioned history of states for the content item" do - results = described_class.present_many(content_items) + it "returns a versioned history of states for the edition" do + results = described_class.present_many(document.editions) expect(results.count).to eq(1) state_history = results.first.fetch("state_history") @@ -165,16 +165,14 @@ describe "#get_warnings" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, user_facing_version: 2, ) end - let(:scope) do - ContentItem.where(content_id: content_id) - end + let(:scope) { document.editions } context "when include_warnings is false" do let(:result) do @@ -191,19 +189,17 @@ described_class.present_many(scope, include_warnings: true) end - context "without a blocking content item" do + context "without a blocking edition" do it "does not include warnings" do expect(result.first["warnings"]).to be_empty end end - context "with a blocking content item" do + context "with a blocking edition" do before do - @blocking_content_item = FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + @blocking_edition = FactoryGirl.create(:live_edition, base_path: base_path, user_facing_version: 1, - locale: "en", ) end diff --git a/spec/presenters/queries/expanded_link_set_spec.rb b/spec/presenters/queries/expanded_link_set_spec.rb index a904ed6b8..4f9a912eb 100644 --- a/spec/presenters/queries/expanded_link_set_spec.rb +++ b/spec/presenters/queries/expanded_link_set_spec.rb @@ -21,10 +21,10 @@ ).links } - context "with content items that are non-renderable" do - let!(:draft_a) { create_content_item(a, "/a", "draft") } - let!(:redirect) { FactoryGirl.create(:redirect_draft_content_item, content_id: b, base_path: '/b') } - let!(:gone) { FactoryGirl.create(:gone_content_item, content_id: c, base_path: '/c') } + context "with editions that are non-renderable" do + let!(:draft_a) { create_edition(a, "/a", factory: :draft_edition) } + let!(:redirect) { create_edition(b, "/b", factory: :redirect_draft_edition) } + let!(:gone) { create_edition(c, "/c", factory: :gone_edition) } let(:state_fallback_order) { [:draft] } @@ -38,14 +38,14 @@ end end - context "with content items in a draft state" do - let!(:draft_a) { create_content_item(a, "/a", "draft") } - let!(:draft_b) { create_content_item(b, "/b", "draft") } - let!(:draft_c) { create_content_item(c, "/c", "draft") } - let!(:draft_d) { create_content_item(d, "/d", "draft") } - let!(:draft_e) { create_content_item(e, "/e", "draft") } - let!(:draft_f) { create_content_item(f, "/f", "draft") } - let!(:draft_g) { create_content_item(g, "/g", "draft") } + context "with editions in a draft state" do + let!(:draft_a) { create_edition(a, "/a", factory: :draft_edition) } + let!(:draft_b) { create_edition(b, "/b", factory: :draft_edition) } + let!(:draft_c) { create_edition(c, "/c", factory: :draft_edition) } + let!(:draft_d) { create_edition(d, "/d", factory: :draft_edition) } + let!(:draft_e) { create_edition(e, "/e", factory: :draft_edition) } + let!(:draft_f) { create_edition(f, "/f", factory: :draft_edition) } + let!(:draft_g) { create_edition(g, "/g", factory: :draft_edition) } let(:state_fallback_order) { [:draft] } @@ -73,7 +73,7 @@ base_path: "/c", links: { parent: [ - a_hash_including(base_path: "/d", details: {}, links: {}) + a_hash_including(base_path: "/d", links: {}) ] })] }) @@ -195,10 +195,10 @@ end end - context "when the depended on content item has no location" do + context "when the depended on edition has no location" do before do create_link(a, b, "parent") - ContentItem.find_by(base_path: '/b').update_attributes!(base_path: nil) + Edition.find_by(base_path: '/b').update_attributes!(base_path: nil) end it "has no web_url" do @@ -210,10 +210,10 @@ end end - context "when the depended on content item does not exist" do + context "when the depended on edition does not exist" do before do create_link(a, b, "parent") - ContentItem.find_by(content_id: b).destroy + Edition.with_document.find_by('documents.content_id': b).destroy end it "does not have a parent" do @@ -222,15 +222,15 @@ end end - context "with content items in different states" do - context "when a content item is in a state that does not match the provided state" do + context "with editions in different states" do + context "when a edition is in a state that does not match the provided state" do before do create_link(a, b, "related") create_link(a, c, "related") - create_content_item(a, "/a", "draft") - create_content_item(b, "/b", "draft") - create_content_item(c, "/c", "published") + create_edition(a, "/a", factory: :draft_edition) + create_edition(b, "/b", factory: :draft_edition) + create_edition(c, "/c") end context "when requested with a draft state" do @@ -254,11 +254,11 @@ end end - context "when a published content item is linked to content in draft" do + context "when a published edition is linked to content in draft" do before do create_link(a, b, "related") - create_content_item(a, "/a-published", "published") - create_content_item(b, "/b-draft", "draft") + create_edition(a, "/a-published") + create_edition(b, "/b-draft", factory: :draft_edition) end context "with a fallback to published" do @@ -278,18 +278,18 @@ end end - context "when one of the recursive content items does not match the provided state" do + context "when one of the recursive editions does not match the provided state" do before do create_link(a, b, "parent") create_link(b, c, "parent") create_link(c, d, "parent") - create_content_item(a, "/a-draft", "draft") - create_content_item(b, "/b-draft", "draft", "en", 2) - create_content_item(d, "/d-draft", "draft") + create_edition(a, "/a-draft", factory: :draft_edition) + create_edition(b, "/b-draft", factory: :draft_edition, version: 2) + create_edition(d, "/d-draft", factory: :draft_edition) - create_content_item(b, "/b-published", "published") - create_content_item(c, "/c-published", "published") + create_edition(b, "/b-published") + create_edition(c, "/c-published") end context "when requested with a draft state" do @@ -316,7 +316,7 @@ end # We need to support an array of states to cater for the DiscardDraft - # command which deletes the draft content item and sends the published item + # command which deletes the draft edition and sends the published item # to the draft content store. This means that we need to try to find a # draft, but fall back to the published item (if it exists). context "when an array of states is provided" do @@ -327,14 +327,14 @@ create_link(b, c, "parent") create_link(c, d, "parent") - create_content_item(a, "/a-draft", "draft") - create_content_item(b, "/b-published", "published") - create_content_item(c, "/c-draft", "draft", "en", 2) - create_content_item(c, "/c-published", "published") - create_content_item(d, "/d-published", "published") + create_edition(a, "/a-draft", factory: :draft_edition) + create_edition(b, "/b-published") + create_edition(c, "/c-draft", factory: :draft_edition, version: 2) + create_edition(c, "/c-published") + create_edition(d, "/d-published") end - it "expands for the content item of the first state that matches" do + it "expands for the edition of the first state that matches" do expect(expanded_links[:parent]).to match([ a_hash_including(base_path: "/b-published", links: { parent: [a_hash_including(base_path: "/c-draft", links: { @@ -352,12 +352,12 @@ before do create_link(a, b, "organisation") - create_content_item(a, "/a", "published", "en") - create_content_item(b, "/b", "published", "en") + create_edition(a, "/a", locale: "en") + create_edition(b, "/b", locale: "en") end context "when a linked item exists in multiple locales" do - let!(:arabic_b) { create_content_item(b, "/b.ar", "published", "ar") } + let!(:arabic_b) { create_edition(b, "/b.ar", locale: "ar") } it "links to the item in the matching locale" do expect(expanded_links[:organisation]).to match([ @@ -368,7 +368,7 @@ context "when the item exists in the matching locale but a fallback state" do let(:state_fallback_order) { [:draft, :published] } - let!(:arabic_b) { create_content_item(b, "/b.ar", "published", "ar") } + let!(:arabic_b) { create_edition(b, "/b.ar", locale: "ar") } it "links to the item in the matching locale" do expect(expanded_links[:organisation]).to match([ @@ -397,11 +397,11 @@ describe "expanding withdrawn dependents" do let(:state_fallback_order) { [:published] } - let!(:published) { FactoryGirl.create(:withdrawn_unpublished_content_item, content_id: a, base_path: '/a') } - let!(:withdrawn_child) { FactoryGirl.create(:withdrawn_unpublished_content_item, content_id: b, base_path: '/b') } - let!(:published_child) { create_content_item(c, "/c") } before do + create_edition(a, "/a", factory: :withdrawn_unpublished_edition) + create_edition(b, "/b", factory: :withdrawn_unpublished_edition) + create_edition(c, "/c") create_link(b, a, "parent") create_link(c, a, "parent") end @@ -422,10 +422,10 @@ let(:state_fallback_order) { [:draft, :published] } before do - create_content_item(a, "/a-draft", "draft") - create_content_item(b, "/b-published") - create_content_item(c, "/c-published") - create_content_item(d, "/d-published") + create_edition(a, "/a-draft", factory: :draft_edition) + create_edition(b, "/b-published") + create_edition(c, "/c-published") + create_edition(d, "/d-published") create_link(d, c, "parent") create_link(c, b, "parent") @@ -457,17 +457,22 @@ end end - context "with a withdrawn content item as a parent" do - let!(:published) { create_content_item(a, "/a", "published") } - let!(:unpublishing) { FactoryGirl.create(:withdrawn_unpublished_content_item, content_id: b, base_path: '/b') } - let!(:unpublishing_not_parent) { FactoryGirl.create(:withdrawn_unpublished_content_item, content_id: c, base_path: '/c') } - + context "with a withdrawn edition as a parent" do let(:state_fallback_order) { [:published, :withdrawn] } + before do + create_edition(a, "/a") + create_edition(b, "/b", factory: :withdrawn_unpublished_edition) + create_edition(c, "/c", factory: :withdrawn_unpublished_edition) + end + context "a simple non-recursive graph" do - it "expands the links for node a correctly" do + before do create_link(a, b, "parent") create_link(a, c, "related") + end + + it "expands the links for node a correctly" do expect(expanded_links[:parent]).to match([a_hash_including('base_path': '/b')]) expect(expanded_links[:related]).to match(nil) end diff --git a/spec/queries/base_path_for_state_spec.rb b/spec/queries/base_path_for_state_spec.rb index a4bbec0c1..6e793ff53 100644 --- a/spec/queries/base_path_for_state_spec.rb +++ b/spec/queries/base_path_for_state_spec.rb @@ -5,23 +5,20 @@ let(:no_conflict_base_path) { "/no-conflict" } describe ".conflict" do - subject { described_class.conflict(content_item_id, state, base_path) } + subject { described_class.conflict(edition_id, state, base_path) } let(:base_path) { conflict_base_path } - context "when content item is a draft" do - let!(:conflict_content_item) do - FactoryGirl.create( - :draft_content_item, - base_path: conflict_base_path, - ) + context "when edition is a draft" do + let!(:conflict_edition) do + FactoryGirl.create(:draft_edition, base_path: conflict_base_path) end - let(:content_item_id) { conflict_content_item.id + 1 } + let(:edition_id) { conflict_edition.id + 1 } let(:state) { "draft" } let(:collision_hash) do { - id: conflict_content_item.id, - content_id: conflict_content_item.content_id, + id: conflict_edition.id, + content_id: conflict_edition.document.content_id, locale: "en" } end @@ -31,20 +28,20 @@ it { is_expected.to be_nil } end - context "when we use this content items base path and content item id" do - let(:content_item_id) { conflict_content_item.id } + context "when we use this editions base path and edition id" do + let(:edition_id) { conflict_edition.id } it { is_expected.to be_nil } end - context "when we use this content items base path and a different content item id" do + context "when we use this editions base path and a different edition id" do it "should be a collision" do is_expected.to match(collision_hash) end %w(published unpublished superseded).each do |state| - context "when the content item is #{state}" do + context "when the edition is #{state}" do before do - conflict_content_item.update(state: state) + conflict_edition.update(state: state) end it { is_expected.to be_nil } @@ -54,20 +51,20 @@ end { - "published" => :live_content_item, - "unpublished" => :unpublished_content_item, + "published" => :live_edition, + "unpublished" => :unpublished_edition, }.each do |state_name, factory| - context "when content item is #{state_name}" do - let!(:conflict_content_item) do + context "when edition is #{state_name}" do + let!(:conflict_edition) do FactoryGirl.create(factory, base_path: conflict_base_path) end - let(:content_item_id) { conflict_content_item.id + 1 } + let(:edition_id) { conflict_edition.id + 1 } let(:state) { state_name } let(:collision_hash) do { - id: conflict_content_item.id, - content_id: conflict_content_item.content_id, + id: conflict_edition.id, + content_id: conflict_edition.document.content_id, locale: "en" } end @@ -77,20 +74,20 @@ it { is_expected.to be_nil } end - context "when we use this content items base path and content item id" do - let(:content_item_id) { conflict_content_item.id } + context "when we use this editions base path and edition id" do + let(:edition_id) { conflict_edition.id } it { is_expected.to be_nil } end - context "when we use this content items base path and a different content item id" do + context "when we use this editions base path and a different edition id" do it "should be a collision" do is_expected.to match(collision_hash) end end context "when the item we are checking against is unpublished with type substitute" do - let(:content_item_id) do - FactoryGirl.create(:substitute_unpublished_content_item).id + let(:edition_id) do + FactoryGirl.create(:substitute_unpublished_edition).id end let(:state) { "unpublished" } it { is_expected.to be_nil } @@ -98,29 +95,27 @@ end end - context "when content item is unpublished with substitute" do - let!(:conflict_content_item) do - FactoryGirl.create( - :substitute_unpublished_content_item, + context "when edition is unpublished with substitute" do + let!(:conflict_edition) do + FactoryGirl.create(:substitute_unpublished_edition, base_path: conflict_base_path ) end - let(:content_item_id) { conflict_content_item.id + 1 } + let(:edition_id) { conflict_edition.id + 1 } let(:state) { "unpublished" } it { is_expected.to be_nil } end - context "when content item is superseded" do - let!(:conflict_content_item) do - FactoryGirl.create( - :superseded_content_item, + context "when edition is superseded" do + let!(:conflict_edition) do + FactoryGirl.create(:superseded_edition, base_path: conflict_base_path ) end - let(:content_item_id) { conflict_content_item.id + 1 } + let(:edition_id) { conflict_edition.id + 1 } let(:state) { "superseded" } it { is_expected.to be_nil } diff --git a/spec/queries/content_dependencies_spec.rb b/spec/queries/content_dependencies_spec.rb index 30276697b..de07360ca 100644 --- a/spec/queries/content_dependencies_spec.rb +++ b/spec/queries/content_dependencies_spec.rb @@ -5,13 +5,13 @@ let(:content_id) { SecureRandom.uuid } let(:locale) { "en" } - let(:state_fallback_order) { %w[published unpublished] } + let(:content_stores) { %w[live] } let(:instance_options) do { content_id: content_id, locale: locale, - state_fallback_order: state_fallback_order, + content_stores: content_stores, } end @@ -22,11 +22,11 @@ it { is_expected.to be_empty } end - context "when there are translations of the content item" do + context "when there are translations of the edition" do before do - create_content_item(content_id, "/a", "published", "en") - create_content_item(content_id, "/a.fr", "published", "fr") - create_content_item(content_id, "/a.es", "published", "es") + create_edition(content_id, "/a", locale: "en") + create_edition(content_id, "/a.fr", locale: "fr") + create_edition(content_id, "/a.es", locale: "es") end context "and we specify locale as en" do @@ -67,19 +67,19 @@ end end - context "when there are draft translations of the content item" do + context "when there are draft translations of the edition" do before do - create_content_item(content_id, "/a", "published", "en") - create_content_item(content_id, "/a.cy", "draft", "cy") + create_edition(content_id, "/a", locale: "en") + create_edition(content_id, "/a.cy", factory: :draft_edition, locale: "cy") end let(:locale) { "en" } - let(:state_fallback_order) { %w[published unpublished] } + let(:content_stores) { %w[live] } it { is_expected.to be_empty } - context "but we requested drafts in state_fallback_order" do - let(:state_fallback_order) { %w[draft published unpublished] } + context "but we requested drafts in content_stores" do + let(:content_stores) { %w[draft live] } let(:translations) do [ [content_id, "cy"], @@ -90,10 +90,10 @@ end end - context "when items link to this content item" do + context "when items link to this edition" do before do - create_content_item(link_1_content_id, "/link-1", "published", "en") - create_content_item(link_2_content_id, "/link-2", "published", "en") + create_edition(link_1_content_id, "/link-1", locale: "en") + create_edition(link_2_content_id, "/link-2", locale: "en") create_link(link_1_content_id, content_id, "organisation") create_link(link_2_content_id, content_id, "organisation") end @@ -110,10 +110,10 @@ it { is_expected.to match_array(links) } end - context "when items in different translations link to this content item" do + context "when items in different translations link to this edition" do before do - create_content_item(link_content_id, "/link", "published", "en") - create_content_item(link_content_id, "/link.cy", "published", "cy") + create_edition(link_content_id, "/link", locale: "en") + create_edition(link_content_id, "/link.cy", locale: "cy") create_link(link_content_id, content_id, "organisation") end @@ -128,10 +128,10 @@ it { is_expected.to match_array(links) } end - context "when items in different states link to this content item" do + context "when items in different states link to this edition" do before do - create_content_item(link_1_content_id, "/link", "published") - create_content_item(link_2_content_id, "/link", "draft") + create_edition(link_1_content_id, "/link") + create_edition(link_2_content_id, "/link", factory: :draft_edition) create_link(link_1_content_id, content_id, "organisation") create_link(link_2_content_id, content_id, "organisation") end @@ -146,8 +146,8 @@ it { is_expected.to match_array(links) } - context "but we use a different state_fallback_order" do - let(:state_fallback_order) { %w[draft published unpublished] } + context "and we include drafts" do + let(:content_stores) { %w[draft live] } let(:links) do [ @@ -160,11 +160,11 @@ end end - context "when a graph of parent items link to this content item" do + context "when a graph of parent items link to this edition" do before do - create_content_item(great_grandparent_content_id, "/great") - create_content_item(grandparent_content_id, "/great/grand") - create_content_item(parent_content_id, "/great/grand/parent") + create_edition(great_grandparent_content_id, "/great") + create_edition(grandparent_content_id, "/great/grand") + create_edition(parent_content_id, "/great/grand/parent") create_link(parent_content_id, content_id, "parent") create_link(grandparent_content_id, parent_content_id, "parent") create_link(great_grandparent_content_id, grandparent_content_id, "parent") @@ -184,9 +184,9 @@ it { is_expected.to match_array(links) } end - context "when this content item has a link to an item with a reverse link type" do + context "when this edition has a link to an item with a reverse link type" do before do - create_content_item(reverse_link_content_id, "/reverse") + create_edition(reverse_link_content_id, "/reverse") create_link(content_id, reverse_link_content_id, "documents") end @@ -201,7 +201,7 @@ context "and there are translations of item that links" do before do - create_content_item(reverse_link_content_id, "/reverse.cy", "published", "cy") + create_edition(reverse_link_content_id, "/reverse.cy", locale: "cy") end let(:links) do @@ -217,17 +217,17 @@ context "when this content has a link to a draft item with a reverse link type" do before do - create_content_item(reverse_link_content_id, "/reverse", "draft") + create_edition(reverse_link_content_id, "/reverse", factory: :draft_edition) create_link(content_id, reverse_link_content_id, "documents") end let(:reverse_link_content_id) { SecureRandom.uuid } - let(:state_fallback_order) { %w[published unpublished] } + let(:content_stores) { %w[live] } it { is_expected.to be_empty } context "and we allow drafts" do - let(:state_fallback_order) { %w[draft published unpublished] } + let(:content_stores) { %w[draft live] } let(:links) do [ diff --git a/spec/queries/get_change_history_spec.rb b/spec/queries/get_change_history_spec.rb index 5eba8df87..182e12be6 100644 --- a/spec/queries/get_change_history_spec.rb +++ b/spec/queries/get_change_history_spec.rb @@ -14,16 +14,16 @@ subject { described_class } let!(:item1) do - FactoryGirl.create(:content_item, details: details, publishing_app: app1) + FactoryGirl.create(:edition, details: details, publishing_app: app1) end let!(:item2) do - FactoryGirl.create(:content_item, details: details, publishing_app: app1) + FactoryGirl.create(:edition, details: details, publishing_app: app1) end let!(:item3) do - FactoryGirl.create(:content_item, details: details, publishing_app: app2) + FactoryGirl.create(:edition, details: details, publishing_app: app2) end let!(:item4) do - FactoryGirl.create(:content_item, details: {}, publishing_app: app2) + FactoryGirl.create(:edition, details: {}, publishing_app: app2) end it "gets application-specific history data" do diff --git a/spec/queries/get_content_collection_spec.rb b/spec/queries/get_content_collection_spec.rb index 43016fdf1..ccfcb6759 100644 --- a/spec/queries/get_content_collection_spec.rb +++ b/spec/queries/get_content_collection_spec.rb @@ -3,35 +3,31 @@ RSpec.describe Queries::GetContentCollection do context "document_type" do before do - FactoryGirl.create( - :draft_content_item, - base_path: '/a', - document_type: 'topic', - schema_name: 'topic', + FactoryGirl.create(:draft_edition, + base_path: "/a", + document_type: "topic", + schema_name: "topic", ) - FactoryGirl.create( - :draft_content_item, - base_path: '/b', - document_type: 'topic', - schema_name: 'topic', + FactoryGirl.create(:draft_edition, + base_path: "/b", + document_type: "topic", + schema_name: "topic", ) - FactoryGirl.create( - :draft_content_item, - base_path: '/c', - document_type: 'mainstream_browse_page', - schema_name: 'mainstream_browse_page', + FactoryGirl.create(:draft_edition, + base_path: "/c", + document_type: "mainstream_browse_page", + schema_name: "mainstream_browse_page", ) - FactoryGirl.create( - :draft_content_item, - base_path: '/d', - document_type: 'another_type', - schema_name: 'another_type', + FactoryGirl.create(:draft_edition, + base_path: "/d", + document_type: "another_type", + schema_name: "another_type", ) end - it "returns the content items matching the type" do + it "returns the editions matching the type" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path locale publication_state), ).call).to match_array([ hash_including("base_path" => "/a", "publication_state" => "draft", "locale" => "en"), @@ -39,7 +35,7 @@ ]) end - it "returns the content items matching all types when given an array" do + it "returns the editions matching all types when given an array" do expect(Queries::GetContentCollection.new( document_types: %w(topic mainstream_browse_page), fields: %w(base_path locale publication_state), @@ -51,22 +47,20 @@ end end - it "returns the content items of the given format, and placeholder_format" do - FactoryGirl.create( - :draft_content_item, - base_path: '/a', - document_type: 'topic', - schema_name: 'topic', + it "returns the editions of the given format, and placeholder_format" do + FactoryGirl.create(:draft_edition, + base_path: "/a", + document_type: "topic", + schema_name: "topic", ) - FactoryGirl.create( - :draft_content_item, - base_path: '/b', - document_type: 'placeholder_topic', - schema_name: 'placeholder_topic' + FactoryGirl.create(:draft_edition, + base_path: "/b", + document_type: "placeholder_topic", + schema_name: "placeholder_topic" ) expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path publication_state), ).call).to match_array([ hash_including("base_path" => "/a", "publication_state" => "draft"), @@ -75,21 +69,19 @@ end it "includes the publishing state of the item" do - FactoryGirl.create( - :draft_content_item, - base_path: '/draft', - document_type: 'topic', - schema_name: 'topic', + FactoryGirl.create(:draft_edition, + base_path: "/draft", + document_type: "topic", + schema_name: "topic", ) - FactoryGirl.create( - :live_content_item, - base_path: '/live', - document_type: 'topic', - schema_name: 'topic', + FactoryGirl.create(:live_edition, + base_path: "/live", + document_type: "topic", + schema_name: "topic", ) expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path publication_state), ).call).to match_array([ hash_including("base_path" => "/draft", "publication_state" => "draft"), @@ -100,8 +92,8 @@ context "when there's no items for the format" do it "returns an empty array" do expect(Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['base_path'], + document_types: "topic", + fields: ["base_path"], ).call.to_a).to eq([]) end end @@ -110,8 +102,8 @@ it "raises an error" do expect { Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['not_existing'], + document_types: "topic", + fields: ["not_existing"], ).call }.to raise_error(CommandError) end @@ -119,34 +111,31 @@ context "filtering by publishing_app" do before do - FactoryGirl.create( - :draft_content_item, - base_path: '/a', - document_type: 'topic', - schema_name: 'topic', - publishing_app: 'publisher' + FactoryGirl.create(:draft_edition, + base_path: "/a", + document_type: "topic", + schema_name: "topic", + publishing_app: "publisher" ) - FactoryGirl.create( - :draft_content_item, - base_path: '/b', - document_type: 'topic', - schema_name: 'topic', - publishing_app: 'publisher' + FactoryGirl.create(:draft_edition, + base_path: "/b", + document_type: "topic", + schema_name: "topic", + publishing_app: "publisher" ) - FactoryGirl.create( - :draft_content_item, - base_path: '/c', - document_type: 'topic', - schema_name: 'topic', - publishing_app: 'whitehall' + FactoryGirl.create(:draft_edition, + base_path: "/c", + document_type: "topic", + schema_name: "topic", + publishing_app: "whitehall" ) end it "returns items corresponding to the publishing_app parameter if present" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(publishing_app publication_state), - filters: { publishing_app: 'publisher' } + filters: { publishing_app: "publisher" } ).call).to match_array([ hash_including("publishing_app" => "publisher", "publication_state" => "draft"), hash_including("publishing_app" => "publisher", "publication_state" => "draft") @@ -155,7 +144,7 @@ it "returns items for all apps if publishing_app is not present" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(publishing_app publication_state) ).call).to match_array([ hash_including("publishing_app" => "publisher", "publication_state" => "draft"), @@ -167,15 +156,35 @@ describe "the locale filter parameter" do before do - FactoryGirl.create(:draft_content_item, base_path: '/content.en', document_type: 'topic', schema_name: 'topic', locale: 'en') - FactoryGirl.create(:draft_content_item, base_path: '/content.ar', document_type: 'topic', schema_name: 'topic', locale: 'ar') - FactoryGirl.create(:live_content_item, base_path: '/content.en', document_type: 'topic', schema_name: 'topic', locale: 'en') - FactoryGirl.create(:live_content_item, base_path: '/content.ar', document_type: 'topic', schema_name: 'topic', locale: 'ar') + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, locale: "en"), + base_path: "/content.en", + document_type: "topic", + schema_name: "topic", + ) + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, locale: "ar"), + base_path: "/content.ar", + document_type: "topic", + schema_name: "topic", + ) + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, locale: "en"), + base_path: "/content.en", + document_type: "topic", + schema_name: "topic", + ) + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, locale: "ar"), + base_path: "/content.ar", + document_type: "topic", + schema_name: "topic", + ) end - it "returns the content items filtered by 'en' locale by default" do + it "returns the editions filtered by 'en' locale by default" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path publication_state), ).call).to match_array([ hash_including("base_path" => "/content.en", "publication_state" => "draft"), @@ -183,22 +192,22 @@ ]) end - it "returns the content items filtered by locale parameter" do + it "returns the editions filtered by locale parameter" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path publication_state), - filters: { locale: 'ar' }, + filters: { locale: "ar" }, ).call).to match_array([ hash_including("base_path" => "/content.ar", "publication_state" => "draft"), hash_including("base_path" => "/content.ar", "publication_state" => "published"), ]) end - it "returns all content items if the locale parameter is 'all'" do + it "returns all editions if the locale parameter is 'all'" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path publication_state), - filters: { locale: 'all' }, + filters: { locale: "all" }, ).call).to match_array([ hash_including("base_path" => "/content.en", "publication_state" => "draft"), hash_including("base_path" => "/content.ar", "publication_state" => "draft"), @@ -217,22 +226,19 @@ draft_2_content_id = SecureRandom.uuid live_1_content_id = SecureRandom.uuid - FactoryGirl.create( - :draft_content_item, - content_id: draft_1_content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: draft_1_content_id), base_path: "/foo", publishing_app: "specialist-publisher" ) - FactoryGirl.create( - :draft_content_item, - content_id: draft_2_content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: draft_2_content_id), base_path: "/bar" ) - FactoryGirl.create( - :live_content_item, - content_id: live_1_content_id, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: live_1_content_id), base_path: "/baz" ) @@ -245,7 +251,7 @@ FactoryGirl.create(:link, link_set: link_set_3, target_content_id: someorg_content_id) end - it "filters content items by organisation" do + it "filters editions by organisation" do result = Queries::GetContentCollection.new( document_types: "guide", filters: { links: { organisations: someorg_content_id } }, @@ -258,7 +264,7 @@ ]) end - it "filters content items by organisation and other filters" do + it "filters editions by organisation and other filters" do result = Queries::GetContentCollection.new( document_types: "guide", filters: { @@ -274,9 +280,9 @@ describe "filtering by state" do before do - FactoryGirl.create(:draft_content_item, base_path: "/draft") - FactoryGirl.create(:live_content_item, base_path: "/published") - FactoryGirl.create(:unpublished_content_item, base_path: "/unpublished") + FactoryGirl.create(:draft_edition, base_path: "/draft") + FactoryGirl.create(:live_edition, base_path: "/published") + FactoryGirl.create(:unpublished_edition, base_path: "/unpublished") end it "returns all content if no filter is provided" do @@ -316,13 +322,27 @@ end context "when details hash is requested" do + before do + FactoryGirl.create(:draft_edition, + base_path: "/z", + details: { foo: :bar }, + document_type: "topic", + schema_name: "topic", + publishing_app: "publisher" + ) + FactoryGirl.create(:draft_edition, + base_path: "/b", + details: { baz: :bat }, + document_type: "placeholder_topic", + schema_name: "placeholder_topic", + publishing_app: "publisher" + ) + end it "returns the details hash" do - FactoryGirl.create(:draft_content_item, base_path: '/z', details: { foo: :bar }, document_type: 'topic', schema_name: 'topic', publishing_app: 'publisher') - FactoryGirl.create(:draft_content_item, base_path: '/b', details: { baz: :bat }, document_type: 'placeholder_topic', schema_name: 'placeholder_topic', publishing_app: 'publisher') expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(details publication_state), - filters: { publishing_app: 'publisher' } + filters: { publishing_app: "publisher" } ).call).to match_array([ hash_including("details" => { "foo" => "bar" }, "publication_state" => "draft"), hash_including("details" => { "baz" => "bat" }, "publication_state" => "draft"), @@ -331,156 +351,180 @@ end describe "search_fields" do - let!(:content_item_foo) { FactoryGirl.create(:live_content_item, base_path: '/bar/foo', document_type: 'topic', schema_name: 'topic', title: "Baz") } - let!(:content_item_zip) { FactoryGirl.create(:live_content_item, base_path: '/baz', document_type: 'topic', schema_name: 'topic', title: 'zip') } + before do + FactoryGirl.create(:live_edition, + base_path: "/bar/foo", + document_type: "topic", + schema_name: "topic", + title: "Baz", + ) + FactoryGirl.create(:live_edition, + base_path: "/baz", + document_type: "topic", + schema_name: "topic", + title: "zip" + ) + end + subject do Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['base_path'], + document_types: "topic", + fields: ["base_path"], search_query: search_query ) end context "base_path and title" do let(:search_query) { "baz" } - it "finds the content item" do + it "finds the edition" do expect(subject.call.map(&:to_hash)).to match_array([{ "base_path" => "/bar/foo" }, { "base_path" => "/baz" }]) end end context "title" do let(:search_query) { "zip" } - it "finds the content item" do + it "finds the edition" do expect(subject.call.map(&:to_hash)).to eq([{ "base_path" => "/baz" }]) end end end describe "pagination" do - context "with multiple content items" do + context "with multiple editions" do before do - FactoryGirl.create(:draft_content_item, base_path: '/a', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-06") - FactoryGirl.create(:draft_content_item, base_path: '/b', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-05") - FactoryGirl.create(:draft_content_item, base_path: '/c', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-04") - FactoryGirl.create(:draft_content_item, base_path: '/d', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-03") - FactoryGirl.create(:live_content_item, base_path: '/live1', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-02") - FactoryGirl.create(:live_content_item, base_path: '/live2', document_type: 'topic', schema_name: 'topic', public_updated_at: "2010-01-01") + [ + ["/a", "2010-01-06"], ["/b", "2010-01-05"], ["/c", "2010-01-04"], ["/d", "2010-01-03"] + ].each do |(base_path, public_updated_at)| + FactoryGirl.create(:draft_edition, + base_path: base_path, + document_type: "topic", + schema_name: "topic", + public_updated_at: public_updated_at, + ) + end + [ + ["/live1", "2010-01-02"], ["/live2", "2010-01-01"] + ].each do |(base_path, public_updated_at)| + FactoryGirl.create(:live_edition, + base_path: base_path, + document_type: "topic", + schema_name: "topic", + public_updated_at: public_updated_at, + ) + end end it "limits the results returned" do - content_items = Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['publishing_app'], + editions = Queries::GetContentCollection.new( + document_types: "topic", + fields: ["publishing_app"], pagination: Pagination.new(offset: 0, per_page: 3) ).call - expect(content_items.count).to eq(3) + expect(editions.count).to eq(3) end it "fetches results from a specified index" do - content_items = Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['base_path'], + editions = Queries::GetContentCollection.new( + document_types: "topic", + fields: ["base_path"], pagination: Pagination.new(offset: 1, per_page: 2) ).call - expect(content_items.first['base_path']).to eq('/b') + expect(editions.first["base_path"]).to eq("/b") end - it "when per_page is higher than results we only receive remaining content items" do - content_items = Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['base_path'], + it "when per_page is higher than results we only receive remaining editions" do + editions = Queries::GetContentCollection.new( + document_types: "topic", + fields: ["base_path"], pagination: Pagination.new(offset: 3, per_page: 8) ).call.to_a - expect(content_items.first['base_path']).to eq('/d') - expect(content_items.last['base_path']).to eq('/live2') + expect(editions.first["base_path"]).to eq("/d") + expect(editions.last["base_path"]).to eq("/live2") end it "returns all items when no pagination params are specified" do - content_items = Queries::GetContentCollection.new( - document_types: 'topic', - fields: ['publishing_app'], + editions = Queries::GetContentCollection.new( + document_types: "topic", + fields: ["publishing_app"], ).call - expect(content_items.count).to eq(6) + expect(editions.count).to eq(6) end end end describe "result order" do before do - FactoryGirl.create(:content_item, base_path: "/c4", title: 'D', public_updated_at: DateTime.parse('2014-06-14')) - FactoryGirl.create(:content_item, base_path: "/c1", title: 'A', public_updated_at: DateTime.parse('2014-06-13')) - FactoryGirl.create(:content_item, base_path: "/c3", title: 'C', public_updated_at: DateTime.parse('2014-06-17')) - FactoryGirl.create(:content_item, base_path: "/c2", title: 'B', public_updated_at: DateTime.parse('2014-06-15')) + FactoryGirl.create(:edition, base_path: "/c4", title: "D", public_updated_at: "2014-06-14") + FactoryGirl.create(:edition, base_path: "/c1", title: "A", public_updated_at: "2014-06-13") + FactoryGirl.create(:edition, base_path: "/c3", title: "C", public_updated_at: "2014-06-17") + FactoryGirl.create(:edition, base_path: "/c2", title: "B", public_updated_at: "2014-06-15") end - it "returns content items in default order" do - content_items = Queries::GetContentCollection.new( - document_types: 'guide', + it "returns editions in default order" do + editions = Queries::GetContentCollection.new( + document_types: "guide", fields: %w(public_updated_at), ).call.to_a - expect(content_items.count).to eq(4) - expect(content_items.first['public_updated_at']).to eq('2014-06-17T00:00:00Z') - expect(content_items.last['public_updated_at']).to eq('2014-06-13T00:00:00Z') + expect(editions.count).to eq(4) + expect(editions.first["public_updated_at"]).to eq("2014-06-17T00:00:00Z") + expect(editions.last["public_updated_at"]).to eq("2014-06-13T00:00:00Z") end - it "returns paginated content items in default order" do - content_items = Queries::GetContentCollection.new( - document_types: 'guide', + it "returns paginated editions in default order" do + editions = Queries::GetContentCollection.new( + document_types: "guide", fields: %w(public_updated_at), pagination: Pagination.new(offset: 2, per_page: 4) ).call.to_a - expect(content_items.first['public_updated_at']).to eq('2014-06-14T00:00:00Z') - expect(content_items.last['public_updated_at']).to eq('2014-06-13T00:00:00Z') + expect(editions.first["public_updated_at"]).to eq("2014-06-14T00:00:00Z") + expect(editions.last["public_updated_at"]).to eq("2014-06-13T00:00:00Z") end end describe "#total" do - it "returns the number of content items" do - FactoryGirl.create(:content_item, base_path: '/a', schema_name: 'topic', document_type: 'topic') - FactoryGirl.create(:content_item, base_path: '/b', schema_name: 'topic', document_type: 'topic') + it "returns the number of editions" do + FactoryGirl.create(:edition, base_path: "/a", schema_name: "topic", document_type: "topic") + FactoryGirl.create(:edition, base_path: "/b", schema_name: "topic", document_type: "topic") expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path locale publication_state), ).total).to eq(2) end - context "when there are multiple versions of the same content item" do + context "when there are multiple versions of the same edition" do before do - content_id = SecureRandom.uuid + document = FactoryGirl.create(:document) - FactoryGirl.create( - :live_content_item, - content_id: content_id, - document_type: 'topic', - schema_name: 'topic', + FactoryGirl.create(:live_edition, + document: document, + document_type: "topic", + schema_name: "topic", user_facing_version: 1, ) - FactoryGirl.create( - :content_item, - content_id: content_id, - document_type: 'topic', - schema_name: 'topic', - state: "draft", + FactoryGirl.create(:draft_edition, + document: document, + document_type: "topic", + schema_name: "topic", user_facing_version: 2, ) end it "returns the latest item only" do expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path locale publication_state), ).total).to eq(1) expect(Queries::GetContentCollection.new( - document_types: 'topic', + document_types: "topic", fields: %w(base_path locale publication_state), ).call.first["publication_state"]).to eq("draft") end diff --git a/spec/queries/get_content_spec.rb b/spec/queries/get_content_spec.rb index 5b89e8a7e..16207f44e 100644 --- a/spec/queries/get_content_spec.rb +++ b/spec/queries/get_content_spec.rb @@ -2,8 +2,10 @@ RSpec.describe Queries::GetContent do let(:content_id) { SecureRandom.uuid } + let(:document) { FactoryGirl.create(:document, content_id: content_id) } + let(:fr_document) { FactoryGirl.create(:document, content_id: content_id, locale: "fr") } - context "when no content item exists for the content_id" do + context "when no edition exists for the content_id" do it "raises a command error" do expect { subject.call(content_id) @@ -11,20 +13,19 @@ end end - context "when a content item exists for the content_id" do + context "when a edition exists for the content_id" do let(:incorrect_version) { 2 } let(:incorrect_locale) { "fr" } before do - FactoryGirl.create(:content_item, - content_id: content_id, + FactoryGirl.create(:edition, + document: document, base_path: "/vat-rates", user_facing_version: 1, - locale: "en", ) end - it "presents the content item" do + it "presents the edition" do result = subject.call(content_id) expect(result).to include( @@ -41,23 +42,23 @@ ) end - context "when a content item for the requested version does not exist" do + context "when a edition for the requested version does not exist" do it "raises a command error" do expect { subject.call(content_id, version: incorrect_version) - }.to raise_error(CommandError, /version: #{incorrect_version} for content item/) + }.to raise_error(CommandError, /version: #{incorrect_version} for document/) end end - context "when a content item for the requested locale does not exist" do + context "when a edition for the requested locale does not exist" do it "raises a command error" do expect { subject.call(content_id, incorrect_locale) - }.to raise_error(CommandError, /locale: #{incorrect_locale} for content item/) + }.to raise_error(CommandError, /locale: #{incorrect_locale} for document/) end end - context "when a content item for the requested version and locale does not exist" do + context "when a edition for the requested version and locale does not exist" do it "raises a command error" do expect { subject.call(content_id, incorrect_locale, version: incorrect_version) @@ -66,72 +67,70 @@ end end - context "when a draft and a live content item exists for the content_id" do + context "when a draft and a live edition exists for the content_id" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, title: "Draft Title", user_facing_version: 2, ) - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, title: "Live Title", user_facing_version: 1, ) end - it "presents the draft content item" do + it "presents the draft edition" do result = subject.call(content_id) expect(result.fetch("title")).to eq("Draft Title") end end - context "when content items exist in non-draft, non-live states" do + context "when editions exist in non-draft, non-live states" do before do - FactoryGirl.create(:content_item, - content_id: content_id, + FactoryGirl.create(:edition, + document: document, user_facing_version: 1, title: "Published Title", state: "published", ) - FactoryGirl.create(:superseded_content_item, - content_id: content_id, + FactoryGirl.create(:superseded_edition, + document: document, user_facing_version: 2, title: "Submitted Title", ) end - it "includes these content items" do + it "includes these editions" do result = subject.call(content_id) expect(result.fetch("title")).to eq("Submitted Title") end end - context "when content items exist in multiple locales" do + context "when editions exist in multiple locales" do before do - FactoryGirl.create(:content_item, - content_id: content_id, + FactoryGirl.create(:edition, + document: fr_document, user_facing_version: 2, title: "French Title", - locale: "fr", ) - FactoryGirl.create(:content_item, - content_id: content_id, + FactoryGirl.create(:edition, + document: document, user_facing_version: 1, title: "English Title", - locale: "en", ) end - it "returns the english content item by default" do + it "returns the english edition by default" do result = subject.call(content_id) expect(result.fetch("title")).to eq("English Title") end - it "filters content items by the specified locale" do + it "filters editions by the specified locale" do result = subject.call(content_id, "fr") expect(result.fetch("title")).to eq("French Title") end @@ -139,13 +138,13 @@ describe "requesting specific versions" do before do - FactoryGirl.create(:superseded_content_item, - content_id: content_id, + FactoryGirl.create(:superseded_edition, + document: document, user_facing_version: 1, ) - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, user_facing_version: 2, ) end diff --git a/spec/queries/get_edition_ids_with_fallbacks_spec.rb b/spec/queries/get_edition_ids_with_fallbacks_spec.rb new file mode 100644 index 000000000..6887a51d8 --- /dev/null +++ b/spec/queries/get_edition_ids_with_fallbacks_spec.rb @@ -0,0 +1,222 @@ +require "rails_helper" + +RSpec.describe Queries::GetEditionIdsWithFallbacks do + describe ".call" do + let(:state_fallback_order) { %w(published) } + let(:locale_fallback_order) { %w(en) } + let(:content_ids) { [] } + let(:options) do + { + state_fallback_order: state_fallback_order, + locale_fallback_order: locale_fallback_order, + } + end + subject { described_class.call(content_ids, options) } + + it { is_expected.to be_a(Array) } + + context "when a edition is in a draft state" do + let(:content_ids) { [SecureRandom.uuid] } + let(:document) { FactoryGirl.create(:document, content_id: content_ids.first) } + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, document: document) + end + + context "and the state_fallback order is [draft]" do + let(:state_fallback_order) { %w(draft) } + it { is_expected.to match_array([draft_edition.id]) } + end + + context "and the state_fallback order is [published]" do + let(:state_fallback_order) { %w(published) } + it { is_expected.to be_empty } + end + + context "and the state_fallback order is [published, draft]" do + let(:state_fallback_order) { %w(published draft) } + it { is_expected.to match_array([draft_edition.id]) } + end + end + + context "when a edition is in draft and unpublished (withdrawn) states" do + let(:content_ids) { [SecureRandom.uuid] } + let(:document) { FactoryGirl.create(:document, content_id: content_ids.first) } + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: document, + user_facing_version: 2, + ) + end + let!(:withdrawn_edition) do + FactoryGirl.create(:withdrawn_unpublished_edition, + document: document, + user_facing_version: 1, + ) + end + + context "and the state_fallback order is [draft, withdrawn]" do + let(:state_fallback_order) { %w(draft withdrawn) } + it { is_expected.to match_array([draft_edition.id]) } + end + + context "and the state_fallback order is [withdrawn, draft]" do + let(:state_fallback_order) { %w(withdrawn draft) } + it { is_expected.to match_array([withdrawn_edition.id]) } + end + end + + context "when a edition is in multiple locales" do + let(:content_ids) { [SecureRandom.uuid] } + let(:fr_document) do + FactoryGirl.create(:document, + content_id: content_ids.first, + locale: "fr", + ) + end + let(:en_document) do + FactoryGirl.create(:document, + content_id: content_ids.first, + locale: "en", + ) + end + let!(:fr_draft_edition) do + FactoryGirl.create(:draft_edition, document: fr_document) + end + let!(:en_draft_edition) do + FactoryGirl.create(:draft_edition, + document: en_document, + user_facing_version: 2, + ) + end + let!(:en_published_edition) do + FactoryGirl.create(:live_edition, + document: en_document, + user_facing_version: 1, + ) + end + + context "and the locale_fallback_order is [fr]" do + let(:locale_fallback_order) { %w(fr) } + + context "and the state_fallback_order is [draft]" do + let(:state_fallback_order) { %w(draft) } + it { is_expected.to match_array(fr_draft_edition.id) } + end + + context "and the state_fallback_order is [published]" do + let(:state_fallback_order) { %w(published) } + it { is_expected.to be_empty } + end + end + + context "and the locale_fallback_order is [fr, en]" do + let(:locale_fallback_order) { %w(fr en) } + + context "and the state_fallback_order is [draft]" do + let(:state_fallback_order) { %w(draft) } + it { is_expected.to match_array(fr_draft_edition.id) } + end + + context "and the state_fallback_order is [published]" do + let(:state_fallback_order) { %w(published) } + it { is_expected.to match_array(en_published_edition.id) } + end + end + + context "and the locale_fallback_order is [en, fr]" do + let(:locale_fallback_order) { %w(en fr) } + + context "and the state_fallback_order is [draft]" do + let(:state_fallback_order) { %w(draft) } + it { is_expected.to match_array(en_draft_edition.id) } + end + + context "and the state_fallback_order is [published]" do + let(:state_fallback_order) { %w(published) } + it { is_expected.to match_array(en_published_edition.id) } + end + end + end + + context "when multiple editions are requested" do + let(:content_ids) { [SecureRandom.uuid, SecureRandom.uuid] } + let(:vat_document) { FactoryGirl.create(:document, content_id: content_ids.first) } + let(:tax_rates_document) { FactoryGirl.create(:document, content_id: content_ids.last) } + let!(:vat_draft_edition) do + FactoryGirl.create(:draft_edition, + document: vat_document, + user_facing_version: 2, + ) + end + let!(:vat_published_edition) do + FactoryGirl.create(:live_edition, + document: vat_document, + user_facing_version: 1, + ) + end + let!(:tax_rates_draft_edition) do + FactoryGirl.create(:draft_edition, + document: tax_rates_document, + user_facing_version: 2, + ) + end + let!(:tax_rates_withdrawn_edition) do + FactoryGirl.create(:withdrawn_unpublished_edition, + document: tax_rates_document, + user_facing_version: 1, + ) + end + + context "and the state_fallback order is [draft, published]" do + let(:state_fallback_order) { %w(draft published) } + let(:expected) { [vat_draft_edition.id, tax_rates_draft_edition.id] } + it { is_expected.to match_array(expected) } + end + + context "and the state_fallback order is [published, draft]" do + let(:state_fallback_order) { %w(published draft) } + let(:expected) { [vat_published_edition.id, tax_rates_draft_edition.id] } + it { is_expected.to match_array(expected) } + end + + context "and the state_fallback order is [withdrawn, draft]" do + let(:state_fallback_order) { %w(withdrawn draft) } + let(:expected) { [vat_draft_edition.id, tax_rates_withdrawn_edition.id] } + it { is_expected.to match_array(expected) } + end + end + + context "when there is a non-renderable document type" do + let(:content_ids) { [SecureRandom.uuid] } + let(:document) { FactoryGirl.create(:document, content_id: content_ids.first) } + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: document, + document_type: "gone", + user_facing_version: 2, + ) + end + let!(:published_edition) do + FactoryGirl.create(:live_edition, + document: document, + user_facing_version: 1, + ) + end + + context "and the state_fallback order is [draft]" do + let(:state_fallback_order) { %w(draft) } + it { is_expected.to be_empty } + end + + context "and the state_fallback order is [published]" do + let(:state_fallback_order) { %w(published) } + it { is_expected.to match_array([published_edition.id]) } + end + + context "and the state_fallback order is [draft, published]" do + let(:state_fallback_order) { %w(draft published) } + it { is_expected.to match_array([published_edition.id]) } + end + end + end +end diff --git a/spec/queries/get_latest_spec.rb b/spec/queries/get_latest_spec.rb index 18fba61d9..f76c01fa8 100644 --- a/spec/queries/get_latest_spec.rb +++ b/spec/queries/get_latest_spec.rb @@ -1,36 +1,40 @@ require "rails_helper" RSpec.describe Queries::GetLatest do - let(:a) { SecureRandom.uuid } - let(:b) { SecureRandom.uuid } - let(:c) { SecureRandom.uuid } + let(:document_a) { FactoryGirl.create(:document) } + let(:document_b) { FactoryGirl.create(:document) } + let(:document_b_fr) do + FactoryGirl.create(:document, content_id: document_b.content_id, locale: "fr") + end + let(:document_c) { FactoryGirl.create(:document) } before do - FactoryGirl.create(:live_content_item, content_id: a, user_facing_version: 2, base_path: "/a2", state: "published") - FactoryGirl.create(:superseded_content_item, content_id: a, user_facing_version: 1, base_path: "/a1", state: "superseded") - FactoryGirl.create(:content_item, content_id: a, user_facing_version: 3, base_path: "/a3") + FactoryGirl.create(:live_edition, document: document_a, user_facing_version: 2, base_path: "/a2") + FactoryGirl.create(:superseded_edition, document: document_a, user_facing_version: 1, base_path: "/a1") + FactoryGirl.create(:draft_edition, document: document_a, user_facing_version: 3, base_path: "/a3") - FactoryGirl.create(:live_content_item, content_id: b, user_facing_version: 1, base_path: "/b1", state: "published") - FactoryGirl.create(:content_item, content_id: b, user_facing_version: 2, locale: "fr", base_path: "/b2", state: "published") + FactoryGirl.create(:live_edition, document: document_b, user_facing_version: 1, base_path: "/b1") + FactoryGirl.create(:live_edition, document: document_b_fr, user_facing_version: 2, base_path: "/b2") - FactoryGirl.create(:live_content_item, content_id: c, user_facing_version: 1, base_path: "/c1", state: "published") - FactoryGirl.create(:content_item, content_id: c, user_facing_version: 2, base_path: "/c2", state: "draft") + FactoryGirl.create(:live_edition, document: document_c, user_facing_version: 1, base_path: "/c1") + FactoryGirl.create(:draft_edition, document: document_c, user_facing_version: 2, base_path: "/c2") end def base_paths(result) result.map(&:base_path) end - it "returns a scope of the latest content_items for the given scope" do - scope = ContentItem.all + it "returns a scope of the latest editions for the given scope" do + scope = Edition.all result = subject.call(scope) expect(base_paths(result)).to match_array(["/a3", "/b1", "/b2", "/c2"]) - scope = scope.where(content_id: [a, b]) + scope = scope.with_document + .where('documents.content_id': [document_a.content_id, document_b.content_id]) result = subject.call(scope) expect(base_paths(result)).to match_array(["/a3", "/b1", "/b2"]) - scope = scope.where(locale: 'fr') + scope = scope.where('documents.locale': 'fr') result = subject.call(scope) expect(base_paths(result)).to match_array(["/b2"]) end diff --git a/spec/queries/get_link_set_spec.rb b/spec/queries/get_link_set_spec.rb index c5d80dead..f2d4066fe 100644 --- a/spec/queries/get_link_set_spec.rb +++ b/spec/queries/get_link_set_spec.rb @@ -17,22 +17,19 @@ let(:related) { [SecureRandom.uuid, SecureRandom.uuid] } before do - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_set: link_set, link_type: "parent", target_content_id: parent.first ) - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_set: link_set, link_type: "related", target_content_id: related.first ) - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_set: link_set, link_type: "related", target_content_id: related.last diff --git a/spec/queries/get_linked_spec.rb b/spec/queries/get_linked_spec.rb index ce5de0950..7fe4a09a6 100644 --- a/spec/queries/get_linked_spec.rb +++ b/spec/queries/get_linked_spec.rb @@ -32,16 +32,16 @@ end end - context "when a content item with no draft exists" do + context "when a edition with no draft exists" do before do - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: "/vat-rules-2020", title: "VAT rules 2020", ) - FactoryGirl.create(:live_content_item, - content_id: target_content_id, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: target_content_id), base_path: "/vat-org", ) end @@ -56,7 +56,7 @@ ).to eq([]) end - context "where another content item is linked to it" do + context "where another edition is linked to it" do before do link_set = FactoryGirl.create(:link_set, content_id: content_id) FactoryGirl.create(:link, link_set: link_set, target_content_id: target_content_id) @@ -72,16 +72,16 @@ end end - context "when a content item with draft exists "do + context "when a document with draft exists "do before do - FactoryGirl.create(:live_content_item, + FactoryGirl.create(:live_edition, :with_draft, - content_id: target_content_id, + document: FactoryGirl.create(:document, content_id: target_content_id), base_path: "/pay-now" ) end - context "but no content item links to it" do + context "but no edition links to it" do it "returns an empty array" do expect( Queries::GetLinked.new( @@ -105,31 +105,29 @@ end end - context "content items link to the wanted content item" do + context "editions link to the wanted edition" do before do - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), title: "VAT and VATy things", base_path: "/vat-rates", ) FactoryGirl.create(:link_set, content_id: content_id, links: [ - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_type: "organisations", target_content_id: target_content_id ) ] ) - content_item = FactoryGirl.create(:live_content_item, + edition = FactoryGirl.create(:live_edition, base_path: '/vatty', - content_id: SecureRandom.uuid, title: "Another VATTY thing" ) FactoryGirl.create(:link_set, - content_id: content_item.content_id, + content_id: edition.document.content_id, links: [ FactoryGirl.create(:link, link_type: "organisations", @@ -185,55 +183,47 @@ context "draft items linking to the wanted draft item" do before do - FactoryGirl.create( - :live_content_item, + FactoryGirl.create(:live_edition, :with_draft, - content_id: another_target_content_id, + document: FactoryGirl.create(:document, content_id: another_target_content_id), base_path: "/send-now" ) - FactoryGirl.create( - :draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), title: "HMRC documents" ) - FactoryGirl.create( - :link_set, + FactoryGirl.create(:link_set, content_id: content_id, links: [ - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_type: "organisations", target_content_id: another_target_content_id ), ] ) - content_item = FactoryGirl.create( - :draft_content_item, + edition = FactoryGirl.create(:draft_edition, base_path: '/other-hmrc-document', - content_id: SecureRandom.uuid, title: "Another HMRC document" ) - FactoryGirl.create( - :link_set, - content_id: content_item.content_id, + FactoryGirl.create(:link_set, + content_id: edition.document.content_id, links: [ - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_type: "organisations", target_content_id: another_target_content_id ), - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_type: "related_links", target_content_id: SecureRandom.uuid ) ] ) end + it "returns array of hashes, with requested fields" do expect( Queries::GetLinked.new( diff --git a/spec/queries/linked_to_spec.rb b/spec/queries/linked_to_spec.rb index 575046195..a677a1292 100644 --- a/spec/queries/linked_to_spec.rb +++ b/spec/queries/linked_to_spec.rb @@ -19,13 +19,13 @@ def create_link_set(content_id, links_hash) it { is_expected.to be_empty } end - context "when our content item links to items but they don't link back" do + context "when our document links to items but they don't link back" do before { create_link_set(content_id, organistion: [SecureRandom.uuid]) } it { is_expected.to be_empty } end - context "when there are links to our content item" do + context "when there are links to our document" do let(:links_to_content_id) { SecureRandom.uuid } let(:linked_to) { [links_to_content_id] } before { create_link_set(links_to_content_id, organistion: [content_id]) } diff --git a/spec/queries/check_for_content_item_preventing_draft_from_being_published_spec.rb b/spec/queries/live_edition_blocking_draft_edition_spec.rb similarity index 59% rename from spec/queries/check_for_content_item_preventing_draft_from_being_published_spec.rb rename to spec/queries/live_edition_blocking_draft_edition_spec.rb index 8885166fc..8931dda4e 100644 --- a/spec/queries/check_for_content_item_preventing_draft_from_being_published_spec.rb +++ b/spec/queries/live_edition_blocking_draft_edition_spec.rb @@ -1,7 +1,8 @@ require "rails_helper" -RSpec.describe Queries::CheckForContentItemPreventingDraftFromBeingPublished do +RSpec.describe Queries::LiveEditionBlockingDraftEdition do let(:content_id) { SecureRandom.uuid } + let(:document) { FactoryGirl.create(:document, content_id: content_id) } let(:base_path) { "/vat-rates" } let(:document_type) { "guide" } @@ -14,36 +15,33 @@ end end - context "with a single content item" do + context "with a single edition" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end include_examples "check succeeds" end - context "with two content items of different locales" do + context "with two editions of different locales" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path + ".en", document_type: document_type, user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: document.content_id, locale: "es"), base_path: base_path + ".es", document_type: document_type, user_facing_version: 1, - locale: "es", ) end @@ -52,21 +50,16 @@ context "with a unpublished item, of type \"substitute\", and a draft at the same base path" do before do - FactoryGirl.create(:unpublished_content_item, - content_id: SecureRandom.uuid, + FactoryGirl.create(:substitute_unpublished_edition, base_path: base_path, document_type: document_type, - unpublishing_type: "substitute", user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end @@ -75,20 +68,17 @@ context "with a published item, with a substitutable document_type, and a draft at the same base path" do before do - FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + FactoryGirl.create(:live_edition, base_path: base_path, document_type: "unpublishing", user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end @@ -99,20 +89,17 @@ let(:document_type) { "unpublishing" } before do - FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + FactoryGirl.create(:live_edition, base_path: base_path, document_type: "guide", user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end @@ -121,82 +108,71 @@ context "with a unpublished item, and a draft at the same base path" do before do - @blocking_content_item = FactoryGirl.create(:gone_unpublished_content_item, - content_id: SecureRandom.uuid, + @blocking_edition = FactoryGirl.create(:gone_unpublished_edition, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:content_item, - content_id: content_id, + FactoryGirl.create(:edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end - it "fails, returning the id of the content item" do - expect(subject).to eq(@blocking_content_item.id) + it "fails, returning the id of the edition" do + expect(subject).to eq(@blocking_edition.id) end end context "with a published item, and a draft at the same base path" do before do - @blocking_content_item = FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + @blocking_edition = FactoryGirl.create(:live_edition, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end - it "fails, returning the id of the content item" do - expect(subject).to eq(@blocking_content_item.id) + it "fails, returning the id of the edition" do + expect(subject).to eq(@blocking_edition.id) end end context "with no base_path" do let(:base_path) { nil } - let!(:blocking_content_item) do - FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + let!(:blocking_edition) do + FactoryGirl.create(:live_edition, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end - let!(:blocking_content_item_2) do - FactoryGirl.create(:live_content_item, - content_id: SecureRandom.uuid, + let!(:blocking_edition_2) do + FactoryGirl.create(:live_edition, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end let!(:content) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, document_type: document_type, user_facing_version: 1, - locale: "en", ) end diff --git a/spec/queries/locales_for_content_items_spec.rb b/spec/queries/locales_for_content_items_spec.rb deleted file mode 100644 index 8158088ad..000000000 --- a/spec/queries/locales_for_content_items_spec.rb +++ /dev/null @@ -1,147 +0,0 @@ -require "rails_helper" - -RSpec.describe Queries::LocalesForContentItems do - def create_content_item( - content_id, - type = :live_content_item, - user_facing_version = 1, - locale = "en", - base_path_prefix = "vat" - ) - FactoryGirl.create( - type, - content_id: content_id, - locale: locale, - base_path: "/#{base_path_prefix}-#{locale}", - user_facing_version: user_facing_version, - ) - end - - describe ".call" do - let(:content_id_1) { SecureRandom.uuid } - let(:content_id_2) { SecureRandom.uuid } - let(:base_content_ids) { [content_id_1, content_id_2] } - let(:content_ids) { base_content_ids } - let(:states) { %w[draft published unpublished] } - let(:include_substitutes) { false } - - subject { described_class.call(content_ids, states, include_substitutes) } - - it { is_expected.to be_a(Array) } - - context "when there are no content items" do - it { is_expected.to be_empty } - end - - context "when there are two live content items in english" do - before do - create_content_item(content_id_1, :live_content_item, 1, "en", "path-1") - create_content_item(content_id_2, :live_content_item, 1, "en", "path-2") - end - - let(:results) do - [ - [content_id_1, "en"], - [content_id_2, "en"], - ] - end - - it { is_expected.to match_array(results) } - end - - context "when there are live content items in multiple locales" do - before do - create_content_item(content_id_1, :live_content_item, 1, "en", "path-1") - create_content_item(content_id_1, :live_content_item, 1, "cy", "path-1") - create_content_item(content_id_1, :live_content_item, 1, "fr", "path-1") - create_content_item(content_id_2, :live_content_item, 1, "es", "path-2") - create_content_item(content_id_2, :live_content_item, 1, "cy", "path-2") - create_content_item(content_id_2, :live_content_item, 1, "de", "path-2") - end - - let(:results) do - [ - [content_id_1, "cy"], - [content_id_1, "en"], - [content_id_1, "fr"], - [content_id_2, "cy"], - [content_id_2, "de"], - [content_id_2, "es"], - ] - end - - it { is_expected.to match_array(results) } - end - - context "when some of the items are drafts" do - before do - create_content_item(content_id_1, :live_content_item, 1, "en", "path-1") - create_content_item(content_id_2, :draft_content_item, 1, "en", "path-2") - end - - let(:results) do - [ - [content_id_1, "en"], - [content_id_2, "en"], - ] - end - - it { is_expected.to match_array(results) } - - context "but we're only filtering on published / unpublished" do - let(:states) { %w[published unpublished] } - - let(:results) do - [ - [content_id_1, "en"], - ] - end - - it { is_expected.to match_array(results) } - end - end - - context "when some of the items are superseded" do - before do - create_content_item(content_id_1, :live_content_item, 1, "en", "path-1") - create_content_item(content_id_2, :superseded_content_item, 1, "en", "path-2") - end - - let(:results) do - [ - [content_id_1, "en"], - ] - end - - it { is_expected.to match_array(results) } - end - - context "when some of the items are unpublished type substite" do - before do - create_content_item(content_id_1, :live_content_item, 1, "en", "path-1") - create_content_item(content_id_2, :substitute_unpublished_content_item, 1, "en", "path-2") - end - - let(:results) do - [ - [content_id_1, "en"], - ] - end - - it { is_expected.to match_array(results) } - - context "and we're including substitutes" do - let(:include_substitutes) { true } - - let(:results) do - [ - [content_id_1, "en"], - [content_id_2, "en"], - ] - end - - it { is_expected.to match_array(results) } - end - end - end -end diff --git a/spec/queries/locales_for_editions_spec.rb b/spec/queries/locales_for_editions_spec.rb new file mode 100644 index 000000000..26894e89e --- /dev/null +++ b/spec/queries/locales_for_editions_spec.rb @@ -0,0 +1,116 @@ +require "rails_helper" + +RSpec.describe Queries::LocalesForEditions do + def create_edition( + content_id, + type = :live_edition, + user_facing_version = 1, + locale = "en", + base_path_prefix = "vat" + ) + FactoryGirl.create(type, + document: Document.find_or_create_by(content_id: content_id, locale: locale), + base_path: "/#{base_path_prefix}-#{locale}", + user_facing_version: user_facing_version, + ) + end + + describe ".call" do + let(:content_id_1) { SecureRandom.uuid } + let(:content_id_2) { SecureRandom.uuid } + let(:base_content_ids) { [content_id_1, content_id_2] } + let(:content_ids) { base_content_ids } + let(:content_stores) { %w[draft live] } + + subject { described_class.call(content_ids, content_stores) } + + it { is_expected.to be_a(Array) } + + context "when there are no editions" do + it { is_expected.to be_empty } + end + + context "when there are two live editions in english" do + before do + create_edition(content_id_1, :live_edition, 1, "en", "path-1") + create_edition(content_id_2, :live_edition, 1, "en", "path-2") + end + + let(:results) do + [ + [content_id_1, "en"], + [content_id_2, "en"], + ] + end + + it { is_expected.to match_array(results) } + end + + context "when there are live editions in multiple locales" do + before do + create_edition(content_id_1, :live_edition, 1, "en", "path-1") + create_edition(content_id_1, :live_edition, 1, "cy", "path-1") + create_edition(content_id_1, :live_edition, 1, "fr", "path-1") + create_edition(content_id_2, :live_edition, 1, "es", "path-2") + create_edition(content_id_2, :live_edition, 1, "cy", "path-2") + create_edition(content_id_2, :live_edition, 1, "de", "path-2") + end + + let(:results) do + [ + [content_id_1, "cy"], + [content_id_1, "en"], + [content_id_1, "fr"], + [content_id_2, "cy"], + [content_id_2, "de"], + [content_id_2, "es"], + ] + end + + it { is_expected.to match_array(results) } + end + + context "when some of the items are drafts" do + before do + create_edition(content_id_1, :live_edition, 1, "en", "path-1") + create_edition(content_id_2, :draft_edition, 1, "en", "path-2") + end + + let(:results) do + [ + [content_id_1, "en"], + [content_id_2, "en"], + ] + end + + it { is_expected.to match_array(results) } + + context "but we're only filtering on live " do + let(:content_stores) { %w[live] } + + let(:results) do + [ + [content_id_1, "en"], + ] + end + + it { is_expected.to match_array(results) } + end + end + + context "when some of the items are superseded" do + before do + create_edition(content_id_1, :live_edition, 1, "en", "path-1") + create_edition(content_id_2, :superseded_edition, 1, "en", "path-2") + end + + let(:results) do + [ + [content_id_1, "en"], + ] + end + + it { is_expected.to match_array(results) } + end + end +end diff --git a/spec/requests/content_item_requests/downstream_requests_spec.rb b/spec/requests/content_item_requests/downstream_requests_spec.rb index 252b1f08b..811c3770b 100644 --- a/spec/requests/content_item_requests/downstream_requests_spec.rb +++ b/spec/requests/content_item_requests/downstream_requests_spec.rb @@ -27,15 +27,15 @@ expect(response).to be_ok, response.body end - context "when a link set exists for the content item" do + context "when a link set exists for the edition" do let(:link_set) do FactoryGirl.create(:link_set, content_id: v2_content_item[:content_id] ) end - let(:target_content_item) { FactoryGirl.create(:content_item, base_path: "/foo", title: "foo") } - let!(:links) { FactoryGirl.create(:link, link_set: link_set, link_type: "parent", target_content_id: target_content_item.content_id) } + let(:target_edition) { FactoryGirl.create(:edition, base_path: "/foo", title: "foo") } + let!(:links) { FactoryGirl.create(:link, link_set: link_set, link_type: "parent", target_content_id: target_edition.document.content_id) } let(:content_item_for_draft_content_store) do v2_content_item.except(:update_type).merge( @@ -73,10 +73,10 @@ .except(:access_limited, :update_type) } - context "when only a draft content item exists for the link set" do + context "when only a draft edition exists for the link set" do before do - draft = FactoryGirl.create(:draft_content_item, - content_id: content_id, + draft = FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: base_path, ) @@ -84,7 +84,7 @@ FactoryGirl.create(:access_limit, users: access_limit_params.fetch(:users), - content_item: draft, + edition: draft, ) end @@ -106,10 +106,10 @@ end end - context "when only a live content item exists for the link set" do + context "when only a live edition exists for the link set" do before do - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: base_path, ) end @@ -138,21 +138,23 @@ end end - context "when draft and live content items exists for the link set" do + context "when draft and live editions exists for the link set" do before do - draft = FactoryGirl.create(:draft_content_item, - content_id: content_id, + document = FactoryGirl.create(:document, content_id: content_id) + + draft = FactoryGirl.create(:draft_edition, + document: document, base_path: base_path, user_facing_version: 2, ) FactoryGirl.create(:access_limit, users: access_limit_params.fetch(:users), - content_item: draft, + edition: draft, ) - FactoryGirl.create(:live_content_item, - content_id: content_id, + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, ) end @@ -181,7 +183,7 @@ end end - context "when a content item does not exist for the link set" do + context "when an edition does not exist for the link set" do it "does not send to either content store" do expect(WebMock).not_to have_requested(:any, /.*content-store.*/) expect(PublishingAPI.service(:draft_content_store)).not_to receive(:put_content_item) @@ -200,11 +202,10 @@ let(:a) { create_link_set } let(:b) { create_link_set } - let!(:draft_a) { create_content_item(a, "/a", "superseded", "en", 1) } - let!(:published_a) { create_content_item(a, "/a", "published", 'en', 2) } - let!(:draft_b) { create_content_item(b, "/b", "draft") } - before do + create_edition(a, "/a", version: 1) + create_edition(a, "/a", factory: :draft_edition, version: 2) + create_edition(b, "/b", factory: :draft_edition) create_link(a, b, "parent") end @@ -219,7 +220,6 @@ end it "doesn't send draft dependencies to the live content store" do - published_a.update_attributes!(state: 'draft', content_store: 'draft') expect(PublishingAPI.service(:live_content_store)).to receive(:put_content_item) .with(a_hash_including(base_path: '/a')) expect(PublishingAPI.service(:live_content_store)).to_not receive(:put_content_item) @@ -229,7 +229,6 @@ end it "doesn't send draft dependencies to the message queue" do - published_a.update_attributes!(state: 'draft', content_store: 'draft') allow(PublishingAPI.service(:live_content_store)).to receive(:put_content_item) expect(PublishingAPI.service(:queue_publisher)).to receive(:send_message) .with(a_hash_including(base_path: '/a')) @@ -243,8 +242,8 @@ context "/v2/publish" do let(:content_id) { SecureRandom.uuid } let!(:draft) { - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: base_path, ) } diff --git a/spec/requests/content_item_requests/downstream_timeouts_spec.rb b/spec/requests/content_item_requests/downstream_timeouts_spec.rb index 6d6aecc2d..e847184d0 100644 --- a/spec/requests/content_item_requests/downstream_timeouts_spec.rb +++ b/spec/requests/content_item_requests/downstream_timeouts_spec.rb @@ -28,20 +28,22 @@ let(:base_path) { "/vat-rates" } before do - FactoryGirl.create(:live_content_item, + document = FactoryGirl.create(:document, content_id: content_id) + + FactoryGirl.create(:live_edition, v2_content_item - .slice(*ContentItem::TOP_LEVEL_FIELDS) - .merge(base_path: base_path, user_facing_version: 1) + .slice(*Edition::TOP_LEVEL_FIELDS) + .merge(base_path: base_path, user_facing_version: 1, document: document) ) - draft = FactoryGirl.create(:draft_content_item, + draft = FactoryGirl.create(:draft_edition, v2_content_item - .slice(*ContentItem::TOP_LEVEL_FIELDS) - .merge(base_path: base_path, user_facing_version: 2) + .slice(*Edition::TOP_LEVEL_FIELDS) + .merge(base_path: base_path, user_facing_version: 2, document: document) ) FactoryGirl.create(:access_limit, - content_item: draft, + edition: draft, users: access_limit_params.fetch(:users), ) end diff --git a/spec/requests/content_item_requests/endpoint_behaviour_spec.rb b/spec/requests/content_item_requests/endpoint_behaviour_spec.rb index 5c5fe7648..09fb73b19 100644 --- a/spec/requests/content_item_requests/endpoint_behaviour_spec.rb +++ b/spec/requests/content_item_requests/endpoint_behaviour_spec.rb @@ -20,12 +20,12 @@ expect(response.status).to eq(200) end - it "responds with the presented content item" do + it "responds with the presented edition" do put "/v2/content/#{content_id}", params: content_item.to_json - updated_content_item = ContentItem.find_by!(content_id: content_id) + updated_edition = Edition.with_document.find_by!("documents.content_id": content_id) presented_content_item = Presenters::Queries::ContentItemPresenter.present( - updated_content_item, + updated_edition, include_warnings: true, ) @@ -76,7 +76,7 @@ context "with the root path as a base_path" do let(:base_path) { "/" } - it "creates the content item" do + it "creates the edition" do put "/v2/content/#{content_id}", params: content_item.to_json expect(response.status).to eq(200) @@ -96,55 +96,47 @@ context "GET /v2/content/:content_id" do let(:content_id) { SecureRandom.uuid } - context "when the content item exists" do - let!(:content_item) { - FactoryGirl.create( - :draft_content_item, - content_id: content_id, - ) - } + context "when the document exists" do + let(:document) { FactoryGirl.create(:document, content_id: content_id) } + let!(:edition) { FactoryGirl.create(:draft_edition, document: document) } it "responds with 200" do get "/v2/content/#{content_id}" expect(response.status).to eq(200) end - it "responds with the presented content item" do + it "responds with the presented edition" do get "/v2/content/#{content_id}" - updated_content_item = ContentItem.find_by!(content_id: content_id) - presented_content_item = Presenters::Queries::ContentItemPresenter.present( - updated_content_item, - include_warnings: true, - ) - - expect(response.body).to eq(presented_content_item.to_json) - end - - it "responds with the presented content item for the correct locale" do - FactoryGirl.create(:draft_content_item, content_id: content_id, locale: "ar") + updated_edition = Edition.with_document.find_by!("documents.content_id": content_id) presented_content_item = Presenters::Queries::ContentItemPresenter.present( - content_item, + updated_edition, include_warnings: true, ) - get "/v2/content/#{content_id}" - expect(response.body).to eq(presented_content_item.to_json) end end - context "when the content item does not exist" do + context "when the document does not exist" do it "responds with 404" do get "/v2/content/#{SecureRandom.uuid}" expect(response.status).to eq(404) end end + + context "when an invalid UUID is used as content_id" do + it "responds with 404" do + expect { + get "/v2/content/INVALID_UUID" + }.to raise_error(ActionController::RoutingError) + end + end end context "/links" do context "PATCH /v2/links/:content_id" do - context "when creating a link set for a content item to be added later" do + context "when creating a link set for a document to be added later" do it "responds with 200" do patch "/v2/links/#{SecureRandom.uuid}", params: { links: {} }.to_json diff --git a/spec/requests/content_item_requests/message_bus_spec.rb b/spec/requests/content_item_requests/message_bus_spec.rb index 9d2588dbd..468380903 100644 --- a/spec/requests/content_item_requests/message_bus_spec.rb +++ b/spec/requests/content_item_requests/message_bus_spec.rb @@ -16,13 +16,13 @@ let(:request_body) { patch_links_attributes.to_json } let(:request_path) { "/v2/links/#{content_id}" } - context "with a live content item" do - let!(:live_content_item) { - FactoryGirl.create(:live_content_item, - content_id: content_id, + context "with a live edition" do + before do + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: base_path, ) - } + end it "sends a message with a 'links' routing key" do expect(DownstreamService).to receive(:broadcast_to_message_queue).with(anything, 'links') @@ -32,13 +32,13 @@ end end - context "with a draft content item" do - let!(:draft_content_item) { - FactoryGirl.create(:draft_content_item, - content_id: content_id, + context "with a draft edition" do + before do + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), base_path: base_path, ) - } + end it "doesn't send any messages" do expect(DownstreamService).to_not receive(:broadcast_to_message_queue) @@ -53,8 +53,8 @@ context "/v2/publish" do before do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), document_type: "guide", schema_name: "guide", base_path: base_path, diff --git a/spec/requests/content_item_requests/reallocating_base_path_spec.rb b/spec/requests/content_item_requests/reallocating_base_path_spec.rb index a86c58bde..1536c9cac 100644 --- a/spec/requests/content_item_requests/reallocating_base_path_spec.rb +++ b/spec/requests/content_item_requests/reallocating_base_path_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Reallocating base paths of content items" do +RSpec.describe "Reallocating base paths of editions" do let(:content_id) { SecureRandom.uuid } let(:base_path) { "/vat-rates" } @@ -9,46 +9,43 @@ end let(:regular_payload) do - FactoryGirl.build(:draft_content_item, - content_id: content_id, + FactoryGirl.build(:draft_edition, + document: FactoryGirl.create(:document, content_id: content_id), ).as_json.deep_symbolize_keys.merge(base_path: base_path) end describe "/v2/content" do - context "when a base path is occupied by a 'regular' content item" do + context "when a base path is occupied by a 'regular' edition" do before do - FactoryGirl.create( - :draft_content_item, + FactoryGirl.create(:draft_edition, base_path: base_path, ) end - it "cannot be replaced by another 'regular' content item" do + it "cannot be replaced by another 'regular' edition" do put "/v2/content/#{content_id}", params: regular_payload.to_json expect(response.status).to eq(422) end end end - describe "publishing a draft which has a different content_id to the published content item on the same base_path" do - let(:draft_content_id) { SecureRandom.uuid } - let(:live_content_id) { SecureRandom.uuid } + describe "publishing a draft which has a different content_id to the published edition on the same base_path" do + let(:draft_document) { FactoryGirl.create(:document) } + let(:live_document) { FactoryGirl.create(:document) } before do stub_request(:put, %r{.*content-store.*/content/.*}) end - context "when both content items are 'regular' content items" do + context "when both editions are 'regular' editions" do before do - draft = FactoryGirl.create( - :draft_content_item, - content_id: draft_content_id, + draft = FactoryGirl.create(:draft_edition, + document: draft_document, base_path: base_path ) - live = FactoryGirl.create( - :live_content_item, - content_id: live_content_id, + live = FactoryGirl.create(:live_edition, + document: live_document, base_path: base_path ) @@ -57,8 +54,8 @@ end it "raises an error" do - post "/v2/content/#{draft_content_id}/publish", - params: { update_type: "major", content_id: draft_content_id }.to_json + post "/v2/content/#{draft_document.content_id}/publish", + params: { update_type: "major", content_id: draft_document.content_id }.to_json expect(response.status).to eq(422) end diff --git a/spec/requests/discard_draft_request_spec.rb b/spec/requests/discard_draft_request_spec.rb index 78a01db5d..991f8f735 100644 --- a/spec/requests/discard_draft_request_spec.rb +++ b/spec/requests/discard_draft_request_spec.rb @@ -3,12 +3,14 @@ RSpec.describe "Discard draft requests", type: :request do let(:content_id) { SecureRandom.uuid } let(:base_path) { "/vat-rates" } + let(:document) { FactoryGirl.create(:document, content_id: content_id) } + let(:fr_document) { FactoryGirl.create(:document, content_id: content_id, locale: "fr") } describe "POST /v2/content/:content_id/discard-draft" do - context "when a draft content item exists" do - let!(:draft_content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + context "when a draft edition exists" do + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: document, title: "draft", base_path: base_path, ) @@ -23,7 +25,7 @@ expect(response.status).to eq(200) end - it "deletes the content item from the draft content store" do + it "deletes the edition from the draft content store" do expect(PublishingAPI.service(:draft_content_store)).to receive(:delete_content_item) .with(base_path) @@ -35,11 +37,10 @@ describe "optional locale parameter" do let(:french_base_path) { "/tva-tarifs" } - let!(:french_draft_content_item) do - FactoryGirl.create(:draft_content_item, - content_id: content_id, + let!(:french_draft_edition) do + FactoryGirl.create(:draft_edition, + document: fr_document, title: "draft", - locale: "fr", base_path: french_base_path, ) end @@ -48,7 +49,7 @@ stub_request(:delete, Plek.find('draft-content-store') + "/content#{french_base_path}") end - it "only deletes the French content item from the draft content store" do + it "only deletes the French edition from the draft content store" do expect(PublishingAPI.service(:draft_content_store)).to receive(:delete_content_item) .with(french_base_path) @@ -60,7 +61,7 @@ end end - context "when a draft content item does not exist" do + context "when a draft edition does not exist" do it "responds with 404" do post "/v2/content/#{content_id}/discard-draft", params: {}.to_json @@ -75,11 +76,9 @@ post "/v2/content/#{content_id}/discard-draft", params: {}.to_json end - context "and a live content item exists" do + context "and a live edition exists" do before do - FactoryGirl.create(:live_content_item, - content_id: content_id, - ) + FactoryGirl.create(:live_edition, document: document) end it "returns a 422" do diff --git a/spec/requests/expanded_links_endpoint_spec.rb b/spec/requests/expanded_links_endpoint_spec.rb index d17f608ee..9e347b8cc 100644 --- a/spec/requests/expanded_links_endpoint_spec.rb +++ b/spec/requests/expanded_links_endpoint_spec.rb @@ -19,38 +19,42 @@ ] } - let(:content_item) { - FactoryGirl.create(:content_item, + let(:edition) { + FactoryGirl.create(:edition, state: "published", document_type: "placeholder", schema_name: "placeholder", title: "Some title", base_path: "/some-path", description: "Some description", - content_id: "10529c0d-f4b3-4c7d-9589-35ba6a6d1a12" + document: FactoryGirl.create(:document, content_id: "10529c0d-f4b3-4c7d-9589-35ba6a6d1a12") ) } it "returns expanded links" do - organisation = FactoryGirl.create(:content_item, + organisation = FactoryGirl.create(:edition, state: "published", document_type: "organisation", schema_name: "organisation", base_path: "/my-super-org", - content_id: "9b5ae6f5-f127-4843-9333-c157a404dd2d", + document: FactoryGirl.create(:document, content_id: "9b5ae6f5-f127-4843-9333-c157a404dd2d") ) link_set = FactoryGirl.create(:link_set, - content_id: content_item.content_id, + content_id: edition.document.content_id, ) - FactoryGirl.create(:link, link_set: link_set, target_content_id: organisation.content_id, link_type: 'organisations') + FactoryGirl.create(:link, + link_set: link_set, + target_content_id: organisation.document.content_id, + link_type: "organisations", + ) - get "/v2/expanded-links/#{content_item.content_id}" + get "/v2/expanded-links/#{edition.document.content_id}" expect(parsed_response).to eql( "version" => 0, - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "expanded_links" => { "organisations" => [ { @@ -76,14 +80,14 @@ it "returns only translations if there are no links" do link_set = FactoryGirl.create(:link_set, - content_id: content_item.content_id, + content_id: edition.document.content_id, ) get "/v2/expanded-links/#{link_set.content_id}" expect(parsed_response).to eql( "version" => 0, - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "expanded_links" => { "available_translations" => translations, }, @@ -92,7 +96,7 @@ it "returns a version if the link set has a version" do link_set = FactoryGirl.create(:link_set, - content_id: content_item.content_id, + content_id: edition.document.content_id, ) FactoryGirl.create(:lock_version, target: link_set, number: 11) @@ -101,7 +105,7 @@ expect(parsed_response).to eql( "version" => 11, - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "expanded_links" => { "available_translations" => translations, }, @@ -109,12 +113,13 @@ end it "returns 404 if the link set is not found" do - get "/v2/expanded-links/I-DO-NOT-EXIST" + content_id = SecureRandom.uuid + get "/v2/expanded-links/#{content_id}" expect(parsed_response).to eql( "error" => { "code" => 404, - "message" => "Could not find link set with content_id: I-DO-NOT-EXIST", + "message" => "Could not find link set with content_id: #{content_id}", } ) end diff --git a/spec/requests/index_spec.rb b/spec/requests/index_spec.rb index b6148e1f4..5ac698693 100644 --- a/spec/requests/index_spec.rb +++ b/spec/requests/index_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "GET /v2/content", type: :request do let!(:policy_1) { - FactoryGirl.create(:content_item, + FactoryGirl.create(:edition, state: "draft", document_type: "policy", schema_name: "policy", @@ -12,7 +12,7 @@ } let!(:policy_2) { - FactoryGirl.create(:content_item, + FactoryGirl.create(:edition, state: "published", document_type: "policy", schema_name: "policy", diff --git a/spec/requests/linkables_spec.rb b/spec/requests/linkables_spec.rb index c6be7ea06..047fe2270 100644 --- a/spec/requests/linkables_spec.rb +++ b/spec/requests/linkables_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "GET /v2/linkables", type: :request do let!(:policy_1) { - FactoryGirl.create(:content_item, + FactoryGirl.create(:edition, state: "draft", document_type: "policy", title: "Policy 1", @@ -14,7 +14,7 @@ } let!(:policy_2) { - FactoryGirl.create(:content_item, + FactoryGirl.create(:edition, state: "published", document_type: "policy", title: "Policy 2", @@ -28,19 +28,19 @@ end end - it "returns the title, content ID, state, internal name and base path for all content items of a given format" do + it "returns the title, content ID, state, internal name and base path for all editions of a given format" do get "/v2/linkables", params: { document_type: "policy" } expect(JSON.parse(response.body, symbolize_names: true)).to match_array([ hash_including( - content_id: policy_1.content_id, + content_id: policy_1.document.content_id, title: "Policy 1", publication_state: "draft", base_path: "/cat-rates", internal_name: "Cat rates (do not use for actual cats)", ), hash_including( - content_id: policy_2.content_id, + content_id: policy_2.document.content_id, title: "Policy 2", publication_state: "published", base_path: "/vat-rates", diff --git a/spec/requests/logging_requests_spec.rb b/spec/requests/logging_requests_spec.rb index 834587757..776f37181 100644 --- a/spec/requests/logging_requests_spec.rb +++ b/spec/requests/logging_requests_spec.rb @@ -21,12 +21,12 @@ end it "adds a request uuid to the message bus" do - draft_content_item = FactoryGirl.create(:draft_content_item, base_path: base_path) + draft_edition = FactoryGirl.create(:draft_edition, base_path: base_path) expect(PublishingAPI.service(:queue_publisher)).to receive(:send_message) .with(hash_including(govuk_request_id: govuk_request_id)) - post("/v2/content/#{draft_content_item.content_id}/publish", params: { update_type: "minor" }.to_json, + post("/v2/content/#{draft_edition.document.content_id}/publish", params: { update_type: "minor" }.to_json, headers: { "HTTP_GOVUK_REQUEST_ID" => "12345-67890" } ) end @@ -37,9 +37,6 @@ let(:a) { create_link_set } let(:b) { create_link_set } - let!(:draft_a) { create_content_item(a, "/a", "draft", "en", 2) } - let!(:draft_b) { create_content_item(b, "/b", "draft") } - let(:params) do v2_content_item.merge( content_id: a, @@ -49,6 +46,8 @@ end before do + create_edition(a, "/a") + create_edition(b, "/b") create_link(a, b, "parent") end @@ -67,7 +66,7 @@ expect(WebMock).to have_requested(:put, /draft-content-store.*content\/b/) .with(headers: { - "GOVUK-Dependency-Resolution-Source-Content-Id" => draft_a.content_id, + "GOVUK-Dependency-Resolution-Source-Content-Id" => a, }) end end diff --git a/spec/requests/lookups_spec.rb b/spec/requests/lookups_spec.rb index 18bca8761..a2e715c99 100644 --- a/spec/requests/lookups_spec.rb +++ b/spec/requests/lookups_spec.rb @@ -25,11 +25,15 @@ end def create_test_content - FactoryGirl.create(:live_content_item, state: "published", base_path: "/published-and-draft-page", content_id: "aa491126-77ed-4e81-91fa-8dc7f74e9657", user_facing_version: 1) - FactoryGirl.create(:content_item, state: "draft", base_path: "/published-and-draft-page", content_id: "aa491126-77ed-4e81-91fa-8dc7f74e9657", user_facing_version: 2) - FactoryGirl.create(:live_content_item, state: "published", base_path: "/only-published-page", content_id: "bbabcd3c-7c45-4403-8490-db51e4bfc4f6") - FactoryGirl.create(:content_item, state: "draft", base_path: "/draft-and-superseded-page", content_id: "dd1bf833-f91c-4e45-9f97-87b165808176", user_facing_version: 2) - FactoryGirl.create(:superseded_content_item, state: "superseded", base_path: "/draft-and-superseded-page", content_id: "dd1bf833-f91c-4e45-9f97-87b165808176", user_facing_version: 1) + doc1 = FactoryGirl.create(:document, content_id: "aa491126-77ed-4e81-91fa-8dc7f74e9657") + doc2 = FactoryGirl.create(:document, content_id: "bbabcd3c-7c45-4403-8490-db51e4bfc4f6") + doc3 = FactoryGirl.create(:document, content_id: "dd1bf833-f91c-4e45-9f97-87b165808176") + + FactoryGirl.create(:live_edition, state: "published", base_path: "/published-and-draft-page", document: doc1, user_facing_version: 1) + FactoryGirl.create(:edition, state: "draft", base_path: "/published-and-draft-page", document: doc1, user_facing_version: 2) + FactoryGirl.create(:live_edition, state: "published", base_path: "/only-published-page", document: doc2) + FactoryGirl.create(:edition, state: "draft", base_path: "/draft-and-superseded-page", document: doc3, user_facing_version: 2) + FactoryGirl.create(:superseded_edition, state: "superseded", base_path: "/draft-and-superseded-page", document: doc3, user_facing_version: 1) end def test_base_paths diff --git a/spec/requests/publish_intent_requests_spec.rb b/spec/requests/publish_intent_requests_spec.rb index 4c101a9d9..646641ab3 100644 --- a/spec/requests/publish_intent_requests_spec.rb +++ b/spec/requests/publish_intent_requests_spec.rb @@ -65,7 +65,7 @@ context "with the root path as a base_path" do let(:base_path) { "/" } - it "creates the content item" do + it "creates the reservation" do put "/publish-intent#{base_path}", params: content_item.to_json expect(response.status).to eq(200) diff --git a/spec/requests/unpublishing_spec.rb b/spec/requests/unpublishing_spec.rb index 1af586fc5..2fe3c9772 100644 --- a/spec/requests/unpublishing_spec.rb +++ b/spec/requests/unpublishing_spec.rb @@ -9,12 +9,13 @@ RSpec.describe "POST /v2/content/:content_id/unpublish", type: :request do let(:content_id) { SecureRandom.uuid } let(:base_path) { "/vat-rates" } - let!(:content_item) { - FactoryGirl.create(:live_content_item, - content_id: content_id, + let!(:document) { FactoryGirl.create(:document, content_id: content_id) } + let!(:edition) do + FactoryGirl.create(:live_edition, + document: document, base_path: base_path, ) - } + end describe "withdrawing" do let(:withdrawal_params) { @@ -40,7 +41,7 @@ expect(response.status).to eq(200), response.body - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(unpublishing.type).to eq("withdrawal") expect(unpublishing.explanation).to eq("Test withdrawal") end @@ -92,7 +93,7 @@ document_type: "redirect", schema_name: "redirect", base_path: base_path, - publishing_app: content_item.publishing_app, + publishing_app: edition.publishing_app, public_updated_at: Time.zone.now.iso8601, redirects: [ { @@ -111,7 +112,7 @@ expect(response.status).to eq(200), response.body - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(unpublishing.type).to eq("redirect") expect(unpublishing.alternative_path).to eq("/new-path") end @@ -164,7 +165,7 @@ base_path: base_path, document_type: "gone", schema_name: "gone", - publishing_app: content_item.publishing_app, + publishing_app: edition.publishing_app, details: { explanation: "Test gone", alternative_path: "/new-path", @@ -185,7 +186,7 @@ expect(response.status).to eq(200), response.body - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(unpublishing.type).to eq("gone") expect(unpublishing.explanation).to eq("Test gone") expect(unpublishing.alternative_path).to eq("/new-path") @@ -236,7 +237,7 @@ expect(response.status).to eq(200), response.body - unpublishing = Unpublishing.find_by(content_item: content_item) + unpublishing = Unpublishing.find_by(edition: edition) expect(unpublishing.type).to eq("vanish") end diff --git a/spec/service_consumers/pact_helper.rb b/spec/service_consumers/pact_helper.rb index 73b338fd6..335b8d7df 100644 --- a/spec/service_consumers/pact_helper.rb +++ b/spec/service_consumers/pact_helper.rb @@ -67,10 +67,11 @@ provider_state "a content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - draft = FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:draft_edition, base_path: "/robots.txt", - content_id: "bed722e6-db68-43e5-9079-063f623335a7", + document: document, title: "Instructions for crawler robots", description: "robots.txt provides rules for which parts of GOV.UK are permitted to be crawled by different bots.", document_type: "special_route", @@ -85,65 +86,57 @@ }, ], ) - - FactoryGirl.create(:lock_version, target: draft, number: 1) end end provider_state "a draft content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - draft = FactoryGirl.create( - :draft_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - ) - FactoryGirl.create(:lock_version, target: draft, number: 1) + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:draft_edition, document: document) end end provider_state "a draft content item exists with content_id bed722e6-db68-43e5-9079-063f623335a7 with a blocking live item at the same path" do set_up do - live = FactoryGirl.create( - :live_content_item, + FactoryGirl.create(:live_edition, + document: FactoryGirl.create(:document), base_path: "/blocking_path", ) - FactoryGirl.create(:lock_version, target: live, number: 1) - draft = FactoryGirl.create( - :draft_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", + + draft_document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:draft_edition, + document: draft_document, base_path: "/blocking_path", ) - FactoryGirl.create(:lock_version, target: draft, number: 1) end end provider_state "a French content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - draft = FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", locale: "fr", ) - FactoryGirl.create(:lock_version, target: draft, number: 1) + + FactoryGirl.create(:draft_edition, document: document) end end provider_state "a published content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - live = FactoryGirl.create( - :live_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7" - ) + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") - FactoryGirl.create(:lock_version, target: live, number: 1) + FactoryGirl.create(:live_edition, document: document) end end provider_state "an unpublished content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - FactoryGirl.create( - :unpublished_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7" - ) + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:unpublished_edition, document: document) end end @@ -151,19 +144,21 @@ set_up do link_set = FactoryGirl.create(:link_set, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 2, ) - linked_organisation = FactoryGirl.create(:content_item, content_id: "20583132-1619-4c68-af24-77583172c070") - FactoryGirl.create(:lock_version, target: link_set, number: 2) - FactoryGirl.create(:link, link_set: link_set, link_type: "organisations", target_content_id: linked_organisation.content_id) + + document = FactoryGirl.create(:document, content_id: "20583132-1619-4c68-af24-77583172c070") + FactoryGirl.create(:edition, document: document) + FactoryGirl.create(:link, link_set: link_set, link_type: "organisations", target_content_id: document.content_id) end end provider_state "empty links exist for content_id bed722e6-db68-43e5-9079-063f623335a7" do set_up do - link_set = FactoryGirl.create(:link_set, + FactoryGirl.create(:link_set, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 2, ) - FactoryGirl.create(:lock_version, target: link_set, number: 2) end end @@ -175,42 +170,42 @@ provider_state "a draft content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7 and locale: fr" do set_up do - draft = FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", locale: "fr", ) - FactoryGirl.create(:lock_version, target: draft, number: 1) + + FactoryGirl.create(:draft_edition, document: document) end end provider_state "a content item exists in multiple locales with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - FactoryGirl.create( - :draft_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - locale: "en", + en_doc = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + fr_doc = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", locale: "fr") + ar_doc = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", locale: "ar") + + FactoryGirl.create(:draft_edition, + document: en_doc, document_type: "topic", schema_name: "topic", - public_updated_at: '2015-01-03', + public_updated_at: "2015-01-03", user_facing_version: 1, ) - FactoryGirl.create( - :draft_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - locale: "fr", + + FactoryGirl.create(:draft_edition, + document: fr_doc, document_type: "topic", schema_name: "topic", - public_updated_at: '2015-01-02', + public_updated_at: "2015-01-02", user_facing_version: 1, ) - FactoryGirl.create( - :draft_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - locale: "ar", + + FactoryGirl.create(:draft_edition, + document: ar_doc, document_type: "topic", schema_name: "topic", - public_updated_at: '2015-01-01', + public_updated_at: "2015-01-01", user_facing_version: 1, ) end @@ -218,21 +213,21 @@ provider_state "a content item exists in with a superseded version with content_id: bed722e6-db68-43e5-9079-063f623335a7" do set_up do - FactoryGirl.create(:superseded_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - locale: "en", + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:superseded_edition, + document: document, document_type: "topic", schema_name: "topic", - public_updated_at: '2015-01-03', + public_updated_at: "2015-01-03", user_facing_version: 1, ) - FactoryGirl.create(:live_content_item, - content_id: "bed722e6-db68-43e5-9079-063f623335a7", - locale: "en", + FactoryGirl.create(:live_edition, + document: document, document_type: "topic", schema_name: "topic", - public_updated_at: '2015-01-03', + public_updated_at: "2015-01-03", user_facing_version: 2, ) end @@ -240,11 +235,12 @@ provider_state "the content item bed722e6-db68-43e5-9079-063f623335a7 is at lock version 3" do set_up do - draft = FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 3, ) - FactoryGirl.create(:lock_version, target: draft, number: 3) + + FactoryGirl.create(:draft_edition, document: document) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("content-store")) + "/content")) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("draft-content-store")) + "/content")) @@ -253,18 +249,18 @@ provider_state "the linkset for bed722e6-db68-43e5-9079-063f623335a7 is at lock version 3" do set_up do - draft = FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 1, ) - FactoryGirl.create(:lock_version, target: draft, number: 1) - linkset = FactoryGirl.create(:link_set, + FactoryGirl.create(:draft_edition, document: document) + + FactoryGirl.create(:link_set, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 3, ) - FactoryGirl.create(:lock_version, target: linkset, number: 3) - stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("content-store")) + "/content")) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("draft-content-store")) + "/content")) end @@ -272,9 +268,13 @@ provider_state "there is content with document_type 'topic'" do set_up do - FactoryGirl.create(:draft_content_item, + document_a = FactoryGirl.create(:document, + content_id: "aaaaaaaa-aaaa-1aaa-aaaa-aaaaaaaaaaaa", + ) + + FactoryGirl.create(:draft_edition, title: 'Content Item A', - content_id: 'aaaaaaaa-aaaa-1aaa-aaaa-aaaaaaaaaaaa', + document: document_a, base_path: '/a-base-path', document_type: "topic", schema_name: "topic", @@ -284,9 +284,13 @@ }, ) - FactoryGirl.create(:live_content_item, + document_b = FactoryGirl.create(:document, + content_id: "bbbbbbbb-bbbb-2bbb-bbbb-bbbbbbbbbbbb", + ) + + FactoryGirl.create(:live_edition, title: 'Content Item B', - content_id: 'bbbbbbbb-bbbb-2bbb-bbbb-bbbbbbbbbbbb', + document: document_b, base_path: '/another-base-path', public_updated_at: '2015-01-01', document_type: "topic", @@ -297,33 +301,34 @@ provider_state "there is content with document_type 'topic' for multiple publishing apps" do set_up do - content_item = FactoryGirl.create( - :draft_content_item, + document_a = FactoryGirl.create(:document) + document_b = FactoryGirl.create(:document) + document_c = FactoryGirl.create(:document) + + FactoryGirl.create(:draft_edition, + document: document_a, title: 'Content Item A', base_path: '/a-base-path', document_type: "topic", schema_name: "topic", ) - FactoryGirl.create(:lock_version, target: content_item, number: 1) - content_item = FactoryGirl.create( - :draft_content_item, + FactoryGirl.create(:draft_edition, + document: document_b, title: 'Content Item B', base_path: '/another-base-path', document_type: "topic", schema_name: "topic", ) - FactoryGirl.create(:lock_version, target: content_item, number: 1) - content_item = FactoryGirl.create( - :draft_content_item, + FactoryGirl.create(:draft_edition, + document: document_c, title: 'Content Item C', base_path: '/yet-another-base-path', document_type: "topic", schema_name: "topic", publishing_app: 'whitehall', ) - FactoryGirl.create(:lock_version, target: content_item, number: 1) end end @@ -333,28 +338,29 @@ content_id2 = "08dfd5c3-d935-4e81-88fd-cfe65b78893d" content_id3 = "e2961462-bc37-48e9-bb98-c981ef1a2d59" - FactoryGirl.create( - :live_content_item, - content_id: content_id1, + document_1 = FactoryGirl.create(:document, content_id: content_id1) + document_2 = FactoryGirl.create(:document, content_id: content_id2) + document_3 = FactoryGirl.create(:document, content_id: content_id3) + + FactoryGirl.create(:live_edition, + document: document_1, user_facing_version: 1, ) - FactoryGirl.create( - :draft_content_item, - content_id: content_id1, + + FactoryGirl.create(:draft_edition, + document: document_1, user_facing_version: 2 ) - FactoryGirl.create( - :live_content_item, - content_id: content_id3, + FactoryGirl.create(:live_edition, + document: document_3, base_path: '/item-b', public_updated_at: '2015-01-02', user_facing_version: 1, ) - FactoryGirl.create( - :live_content_item, - content_id: content_id2, + FactoryGirl.create(:live_edition, + document: document_2, base_path: '/item-a', public_updated_at: '2015-01-01', user_facing_version: 1, @@ -370,9 +376,10 @@ provider_state "a content item exists with content_id: bed722e6-db68-43e5-9079-063f623335a7 and it has details" do set_up do - FactoryGirl.create( - :draft_content_item, - content_id: 'bed722e6-db68-43e5-9079-063f623335a7', + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7") + + FactoryGirl.create(:draft_edition, + document: document, document_type: "topic", schema_name: "topic", details: { foo: :bar }, @@ -382,12 +389,13 @@ provider_state "the content item bed722e6-db68-43e5-9079-063f623335a7 is at version 3" do set_up do - FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", - lock_version: 3 + stale_lock_version: 3, ) + FactoryGirl.create(:draft_edition, document: document) + stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("content-store")) + "/content")) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("draft-content-store")) + "/content")) end @@ -395,12 +403,13 @@ provider_state "the published content item bed722e6-db68-43e5-9079-063f623335a7 is at version 3" do set_up do - FactoryGirl.create( - :live_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", - lock_version: 3 + stale_lock_version: 3 ) + FactoryGirl.create(:live_edition, document: document) + stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("content-store")) + "/content")) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("draft-content-store")) + "/content")) end @@ -408,19 +417,18 @@ provider_state "the linkset for bed722e6-db68-43e5-9079-063f623335a7 is at version 3" do set_up do - FactoryGirl.create( - :draft_content_item, + document = FactoryGirl.create(:document, content_id: "bed722e6-db68-43e5-9079-063f623335a7", - lock_version: 1 + stale_lock_version: 1, ) - link_set = FactoryGirl.create( - :link_set, + FactoryGirl.create(:draft_edition, document: document) + + FactoryGirl.create(:link_set, content_id: "bed722e6-db68-43e5-9079-063f623335a7", + stale_lock_version: 3, ) - FactoryGirl.create(:lock_version, target: link_set, number: 3) - stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("content-store")) + "/content")) stub_request(:put, Regexp.new('\A' + Regexp.escape(Plek.find("draft-content-store")) + "/content")) end @@ -428,17 +436,13 @@ provider_state "there are live content items with base_paths /foo and /bar" do set_up do - FactoryGirl.create( - :live_content_item, - base_path: '/foo', - content_id: '08f86d00-e95f-492f-af1d-470c5ba4752e', - ) + document_1 = FactoryGirl.create(:document, content_id: "08f86d00-e95f-492f-af1d-470c5ba4752e") - FactoryGirl.create( - :live_content_item, - base_path: '/bar', - content_id: 'ca6c58a6-fb9d-479d-b3e6-74908781cb18', - ) + FactoryGirl.create(:live_edition, base_path: '/foo', document: document_1) + + document_2 = FactoryGirl.create(:document, content_id: "ca6c58a6-fb9d-479d-b3e6-74908781cb18") + + FactoryGirl.create(:live_edition, base_path: '/bar', document: document_2) end end end diff --git a/spec/services/downstream_service_spec.rb b/spec/services/downstream_service_spec.rb index a577f7303..3c217bc6d 100644 --- a/spec/services/downstream_service_spec.rb +++ b/spec/services/downstream_service_spec.rb @@ -258,7 +258,7 @@ context "conflict" do before do - FactoryGirl.create(:unpublished_content_item, base_path: "/test") + FactoryGirl.create(:unpublished_edition, base_path: "/test") end it "returns true" do diff --git a/spec/substitution_helper_spec.rb b/spec/substitution_helper_spec.rb index 86409b36b..784d4179f 100644 --- a/spec/substitution_helper_spec.rb +++ b/spec/substitution_helper_spec.rb @@ -6,7 +6,7 @@ let(:existing_base_path) { "/vat-rates" } let!(:existing_item) { - FactoryGirl.create(:draft_content_item, + FactoryGirl.create(:draft_edition, document_type: existing_document_type, base_path: existing_base_path, ) @@ -31,15 +31,15 @@ end context "when the content_id is the same as the existing item" do - let(:new_content_id) { existing_item.content_id } + let(:new_content_id) { existing_item.document.content_id } it "does not discard the existing draft" do - expect(ContentItem.exists?(id: existing_item.id)).to eq(true) + expect(Edition.exists?(id: existing_item.id)).to eq(true) end context "when the existing item is published" do let!(:existing_item) { - FactoryGirl.create(:live_content_item, + FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) @@ -58,22 +58,22 @@ let(:existing_document_type) { "gone" } it "discards the existing draft" do - expect(ContentItem.exists?(id: existing_item.id)).to eq(false) + expect(Edition.exists?(id: existing_item.id)).to eq(false) end it "doesn't unpublish any other items" do - live_item = FactoryGirl.create(:live_content_item, + live_item = FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) - french_item = FactoryGirl.create(:draft_content_item, + french_item = FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, locale: "fr"), document_type: existing_document_type, base_path: existing_base_path, - locale: "fr", ) - item_elsewhere = FactoryGirl.create(:draft_content_item, + item_elsewhere = FactoryGirl.create(:draft_edition, document_type: existing_document_type, base_path: "/somewhere-else", ) @@ -85,7 +85,7 @@ context "when the existing item is published" do let!(:existing_item) { - FactoryGirl.create(:live_content_item, + FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) @@ -101,22 +101,22 @@ let(:new_document_type) { "gone" } it "discards the existing draft" do - expect(ContentItem.exists?(id: existing_item.id)).to eq(false) + expect(Edition.exists?(id: existing_item.id)).to eq(false) end it "doesn't unpublish any other items" do - live_item = FactoryGirl.create(:live_content_item, + live_item = FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) - french_item = FactoryGirl.create(:draft_content_item, + french_item = FactoryGirl.create(:draft_edition, + document: FactoryGirl.create(:document, locale: "fr"), document_type: existing_document_type, base_path: existing_base_path, - locale: "fr", ) - item_elsewhere = FactoryGirl.create(:draft_content_item, + item_elsewhere = FactoryGirl.create(:draft_edition, document_type: existing_document_type, base_path: "/somewhere-else", ) @@ -128,7 +128,7 @@ context "when the existing item is published" do let!(:existing_item) { - FactoryGirl.create(:live_content_item, + FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) @@ -142,12 +142,12 @@ context "when neither item has a document_type that is substitutable" do it "does not discard the existing draft" do - expect(ContentItem.exists?(id: existing_item.id)).to eq(true) + expect(Edition.exists?(id: existing_item.id)).to eq(true) end context "when the existing item is published" do let!(:existing_item) { - FactoryGirl.create(:live_content_item, + FactoryGirl.create(:live_edition, document_type: existing_document_type, base_path: existing_base_path, ) diff --git a/spec/support/dependency_resolution_helper.rb b/spec/support/dependency_resolution_helper.rb index 4539acb5a..dbd631f61 100644 --- a/spec/support/dependency_resolution_helper.rb +++ b/spec/support/dependency_resolution_helper.rb @@ -4,36 +4,24 @@ def create_link_set link_set.content_id end - def create_content_item( + def create_edition( content_id, base_path, - state = "published", - locale = "en", - version = 1 + factory: :live_edition, + locale: "en", + version: 1 ) - FactoryGirl.create( - :content_item, - content_id: content_id, + FactoryGirl.create(factory, + document: Document.find_or_create_by(content_id: content_id, locale: locale), base_path: base_path, - state: state, - content_store: content_store_for(state), - locale: locale, - document_type: 'topical_event', - details: {}, user_facing_version: version, ) end - def content_store_for(state) - return if state == 'superseded' - state == 'draft' ? 'draft' : 'live' - end - def create_link(from, to, link_type, link_position = 0) link_set = LinkSet.find_or_create_by(content_id: from) - FactoryGirl.create( - :link, + FactoryGirl.create(:link, link_set: link_set, target_content_id: to, link_type: link_type, diff --git a/spec/support/routes_and_redirects_validator.rb b/spec/support/routes_and_redirects_validator.rb index c34cdc0e0..b6a627fa1 100644 --- a/spec/support/routes_and_redirects_validator.rb +++ b/spec/support/routes_and_redirects_validator.rb @@ -1,7 +1,7 @@ RSpec.shared_examples_for RoutesAndRedirectsValidator do describe "routes validations" do it "is invalid when a route is not below the base path" do - content_item.routes = [ + edition.routes = [ { path: subject.base_path, type: "exact" }, { path: "/wrong-path", type: "exact" }, ] @@ -11,7 +11,7 @@ end it "must have unique paths" do - content_item.routes = [ + edition.routes = [ { path: subject.base_path, type: "exact" }, { path: subject.base_path, type: "exact" }, ] @@ -21,29 +21,29 @@ end it "must have a type" do - content_item.routes = [{ path: subject.base_path }] + edition.routes = [{ path: subject.base_path }] expect(subject).to be_invalid end it "must have a path" do - content_item.routes = [{ type: "exact" }] + edition.routes = [{ type: "exact" }] expect(subject).to be_invalid end it "must have a valid type" do - content_item.routes = [{ path: subject.base_path, type: "unsupported" }] + edition.routes = [{ path: subject.base_path, type: "unsupported" }] expect(subject).to be_invalid end it "cannot have extra keys" do - content_item.routes = [{ path: subject.base_path, type: "exact", foo: "bar" }] + edition.routes = [{ path: subject.base_path, type: "exact", foo: "bar" }] expect(subject).to be_invalid expect(subject.errors[:routes]).to eq(["unsupported keys: foo"]) end it "is valid with a dashed locale" do - content_item.routes = [ + edition.routes = [ { path: subject.base_path, type: "exact" }, { path: "#{subject.base_path}.es-419", type: "exact" }, ] @@ -52,7 +52,7 @@ end it "must contain valid absolute paths" do - content_item.routes = [ + edition.routes = [ { path: subject.base_path, type: "exact" }, { path: "#{subject.base_path}/ not valid", type: "exact" }, ] @@ -62,39 +62,39 @@ end it "does not throw an error when routes is nil rather than an empty array" do - content_item.routes = nil + edition.routes = nil expect { subject.valid? }.to_not raise_error end context "for a redirect item" do before do - content_item.document_type = "redirect" - content_item.routes = [] + edition.document_type = "redirect" + edition.routes = [] end it "must not have routes" do - content_item.routes = [{ path: "#{subject.base_path}/foo", type: "exact" }] + edition.routes = [{ path: "#{subject.base_path}/foo", type: "exact" }] expect(subject).to be_invalid end end context "for a non-redirect item" do before do - content_item.document_type = "guide" + edition.document_type = "guide" end it "must have routes" do - content_item.routes = [{ path: subject.base_path, type: "exact" }] + edition.routes = [{ path: subject.base_path, type: "exact" }] expect(subject).to be_valid end it "must include the base path" do - content_item.routes = [{ path: "#{subject.base_path}/foo", type: "exact" }] + edition.routes = [{ path: "#{subject.base_path}/foo", type: "exact" }] expect(subject).to be_invalid end it "does not throw an error when routes is nil rather than an empty array" do - content_item.routes = nil + edition.routes = nil expect { subject.valid? }.to_not raise_error end end @@ -102,7 +102,7 @@ describe "redirects validations" do it "is invalid when a redirect is not below the base path" do - content_item.redirects = [ + edition.redirects = [ { path: "/wrong-path", type: "exact", destination: "/bar" }, ] @@ -111,7 +111,7 @@ end it "must have unique paths" do - content_item.redirects = [ + edition.redirects = [ { path: "#{subject.base_path}/foo", type: "exact", destination: "/bar" }, { path: "#{subject.base_path}/foo", type: "exact", destination: "/bar" }, ] @@ -121,38 +121,38 @@ end it "must have a valid type" do - content_item.redirects = [{ path: subject.base_path, type: "unsupported", destination: "/foo" }] + edition.redirects = [{ path: subject.base_path, type: "unsupported", destination: "/foo" }] expect(subject).to be_invalid end it "cannot have extra keys" do - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: "/foo", foo: "bar" }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: "/foo", foo: "bar" }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["unsupported keys: foo"]) end it "is valid with a dashed locale" do - content_item.redirects = [{ path: "#{subject.base_path}.es-419", type: "exact", destination: "/foo" }] + edition.redirects = [{ path: "#{subject.base_path}.es-419", type: "exact", destination: "/foo" }] expect(subject).to be_valid end it "must contain valid absolute paths" do - content_item.redirects = [{ path: "#{subject.base_path}/ not valid", type: "exact", destination: "/foo" }] + edition.redirects = [{ path: "#{subject.base_path}/ not valid", type: "exact", destination: "/foo" }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["is not a valid absolute URL path"]) end it "does not throw an error when redirects is nil rather than an empty array" do - content_item.redirects = nil + edition.redirects = nil expect { subject.valid? }.to_not raise_error end context "when destination is external url" do it "is invalid if it is not an external gov.uk campaign url" do ["https://www.example.com/foo/bar", "https://www.gov.uk/foo/bar"].each do |destination| - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["is not a valid redirect destination"]) @@ -161,7 +161,7 @@ it "is invalid if the url is a malformed gov.uk campaign external url" do ["://new-vat-rates.campaign.gov.uk/", "http:new-vat-rates.campaign.gov.uk/", "httpsnew-vat-rates.campaign.gov.uk/", "https://new_vat-rates.campaign.gov.uk/", "http://.campaign.gov.uk/", "http://new-vat-rates.campaign.gov.uk/path/to/your/new/vat-rates", "http://new-vat-rates.campaignjservicepgov.uk/path/to/your/new/vat-rates", "https://fakesite.net/.new-vat-rates.campaign.gov.uk/path/to/your/new/vat-rates", "ftp://new-vat-rates.campaign.gov.uk/"].each do |destination| - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["is not a valid redirect destination"]) @@ -170,7 +170,7 @@ it "is valid if the url is a wellformed gov.uk campaign external url" do ["https://new-vat-rates.campaign.gov.uk/", "https://new-vat-rates.campaign.gov.uk/path/to/your/new/vat-rates", "https://new-vat-rates.campaign.gov.uk/path/to/your/new/vat-rates?q=123&&a=23344"].each do |destination| - content_item.redirects = [{ path: "#{subject.base_path}/new", type: "exact", destination: destination }] + edition.redirects = [{ path: "#{subject.base_path}/new", type: "exact", destination: destination }] expect(subject).to be_valid expect(subject.errors[:redirects]).to eq([]) @@ -180,7 +180,7 @@ context "when the type is 'prefix'" do it "must contain valid absolute paths for destinations" do - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "prefix", destination: "not valid" }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "prefix", destination: "not valid" }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["is not a valid absolute URL path"]) @@ -190,14 +190,14 @@ context "when the type is 'exact'" do it "is valid with an optional query string and fragment in destination" do %w(/foo/bar /foo?bar=baz /foo/bar#baz).each do |destination| - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] expect(subject).to be_valid end end it "is invalid with an non-absolute url" do ["foo/bar", "/url with spaces", "fdjkdfjkljsdaf"].each do |destination| - content_item.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] + edition.redirects = [{ path: "#{subject.base_path}/foo", type: "exact", destination: destination }] expect(subject).to be_invalid expect(subject.errors[:redirects]).to eq(["is not a valid redirect destination"]) @@ -207,34 +207,34 @@ context "for a redirect item" do before do - content_item.schema_name = "redirect" - content_item.document_type = "redirect" - content_item.routes = [] + edition.schema_name = "redirect" + edition.document_type = "redirect" + edition.routes = [] end it "must have redirects" do - content_item.redirects = [{ path: subject.base_path, type: "exact", destination: "/foo" }] + edition.redirects = [{ path: subject.base_path, type: "exact", destination: "/foo" }] expect(subject).to be_valid end it "must include the base path" do - content_item.redirects = [{ path: "#{subject.base_path}/bar", type: "exact", destination: "/bar" }] + edition.redirects = [{ path: "#{subject.base_path}/bar", type: "exact", destination: "/bar" }] expect(subject).to be_invalid end end context "for a non-redirect item" do before do - content_item.document_type = "guide" + edition.document_type = "guide" end it "can have redirects" do - content_item.redirects = [{ path: "#{subject.base_path}/bar", type: "exact", destination: "/bar" }] + edition.redirects = [{ path: "#{subject.base_path}/bar", type: "exact", destination: "/bar" }] expect(subject).to be_valid end it "does not throw an error when redirects is nil rather than an empty array" do - content_item.redirects = nil + edition.redirects = nil expect { subject.valid? }.to_not raise_error end end @@ -242,8 +242,8 @@ describe "validations that cross-over between routes and redirects" do it "does not allow redirects to duplicate any of the routes" do - content_item.routes = [{ path: subject.base_path, type: "exact" }] - content_item.redirects = [{ path: subject.base_path, type: "exact", destination: "/foo" }] + edition.routes = [{ path: subject.base_path, type: "exact" }] + edition.redirects = [{ path: subject.base_path, type: "exact", destination: "/foo" }] expect(subject).to be_invalid end diff --git a/spec/support/transactional_command.rb b/spec/support/transactional_command.rb index c0c5a58d5..c2b0222da 100644 --- a/spec/support/transactional_command.rb +++ b/spec/support/transactional_command.rb @@ -15,17 +15,17 @@ module TransactionalCommand it "wraps the command in a transaction" do allow_any_instance_of(described_class).to receive(:call) do - FactoryGirl.create(:content_item, state: "published") + FactoryGirl.create(:live_edition) raise "Uh oh, command failed half-way through processing" end - previous_count = ContentItem.count + previous_count = Edition.count expect { described_class.call(payload) }.to raise_error(/half-way through/) - new_count = ContentItem.count + new_count = Edition.count expect(new_count).to eq(previous_count), "The transaction should have been rolled back" diff --git a/spec/validators/base_path_for_state_validator_spec.rb b/spec/validators/base_path_for_state_validator_spec.rb index b208d2683..924648731 100644 --- a/spec/validators/base_path_for_state_validator_spec.rb +++ b/spec/validators/base_path_for_state_validator_spec.rb @@ -4,16 +4,15 @@ let(:state_name) { "draft" } let(:base_path) { "/vat-rates" } - let(:content_item) do - FactoryGirl.build( - :content_item, + let(:edition) do + FactoryGirl.build(:edition, state: state_name, base_path: base_path, ) end describe ".validate" do - subject(:validate) { described_class.new.validate(content_item) } + subject(:validate) { described_class.new.validate(edition) } context "when state is nil" do let(:state_name) { nil } @@ -25,24 +24,29 @@ it { is_expected.to be_nil } end - context "when there are multiple content items" do + context "when there are multiple editions" do let(:conflict_content_id) { SecureRandom.uuid } let(:conflict_state_name) { "draft" } let(:conflict_base_path) { "/vat-rates-2016" } let(:conflict_locale) { "en" } - let!(:conflict_content_item) do - FactoryGirl.create( - :content_item, + let(:conflict_document) do + FactoryGirl.create(:document, content_id: conflict_content_id, + locale: conflict_locale, + ) + end + + let!(:conflict_edition) do + FactoryGirl.create(:edition, + document: conflict_document, state: conflict_state_name, base_path: conflict_base_path, - locale: conflict_locale, user_facing_version: 2, ) end - before { content_item.base_path = conflict_base_path } + before { edition.base_path = conflict_base_path } context "when state is draft" do let(:state_name) { "draft" } @@ -54,8 +58,8 @@ end before { validate } - it "adds the error to content_item attribute" do - expect(content_item.errors[:base]).to eq([expected_error]) + it "adds the error to edition attribute" do + expect(edition.errors[:base]).to eq([expected_error]) end end end @@ -77,8 +81,8 @@ end before { validate } - it "adds the error to content_item attribute" do - expect(content_item.errors[:base]).to eq([expected_error]) + it "adds the error to edition attribute" do + expect(edition.errors[:base]).to eq([expected_error]) end end end diff --git a/spec/validators/state_for_document_validator_spec.rb b/spec/validators/state_for_document_validator_spec.rb new file mode 100644 index 000000000..892795e22 --- /dev/null +++ b/spec/validators/state_for_document_validator_spec.rb @@ -0,0 +1,66 @@ +require "rails_helper" + +RSpec.describe StateForDocumentValidator do + let(:state_name) { "draft" } + let(:document) { FactoryGirl.create(:document) } + + let(:edition) do + FactoryGirl.build(:edition, + state: state_name, + document: document, + ) + end + + describe "#validate" do + subject(:validate) { described_class.new.validate(edition) } + + context "when document is nil" do + before { edition.document = nil } + it { is_expected.to be_nil } + end + + context "when state is nil" do + before { edition.state = nil } + it { is_expected.to be_nil } + end + + context "when state and document can conflict" do + [ + { factory: :draft_edition, state: "draft", scenario: "both items are drafts" }, + { factory: :live_edition, state: "published", scenario: "both items are published" }, + { factory: :live_edition, state: "unpublished", scenario: "existing item is published and new item is unpublished" }, + { factory: :unpublished_edition, state: "published", name: "existing item is unpublished and new item is published" }, + ].each do |hash| + context "when #{hash[:scenario]}" do + let!(:conflict_edition) { + FactoryGirl.create(hash[:factory], + document: document, + ) + } + let(:state_name) { hash[:state] } + let(:expected_error) do + "state=#{hash[:state]} and document=#{document.id} conflicts " + + "with edition id=#{conflict_edition.id}" + end + + before do + validate + end + + it "adds the error to the base attribute" do + expect(edition.errors[:base]).to eq([expected_error]) + end + end + end + + context "when state is superseded" do + let!(:conflict_edition) { + FactoryGirl.create(:superseded_edition, document: document) + } + let(:state_name) { "superseded" } + + it { is_expected.to be_nil } + end + end + end +end diff --git a/spec/validators/state_for_locale_validator_spec.rb b/spec/validators/state_for_locale_validator_spec.rb deleted file mode 100644 index 670f3fd0b..000000000 --- a/spec/validators/state_for_locale_validator_spec.rb +++ /dev/null @@ -1,77 +0,0 @@ -require "rails_helper" - -RSpec.describe StateForLocaleValidator do - let(:state_name) { "draft" } - let(:locale) { "en" } - - let(:content_item) do - FactoryGirl.build( - :content_item, - state: state_name, - locale: locale, - ) - end - - describe "#validate" do - subject(:validate) { described_class.new.validate(content_item) } - - context "when locale is nil" do - before { content_item.locale = nil } - it { is_expected.to be_nil } - end - - context "when state is nil" do - before { content_item.state = nil } - it { is_expected.to be_nil } - end - - context "when version, state and content_id can conflict" do - [ - { factory: :draft_content_item, state: "draft", scenario: "both items are drafts" }, - { factory: :live_content_item, state: "published", scenario: "both items are published" }, - { factory: :live_content_item, state: "unpublished", scenario: "existing item is published and new item is unpublished" }, - { factory: :unpublished_content_item, state: "published", name: "existing item is unpublished and new item is published" }, - ].each do |hash| - context "when #{hash[:scenario]}" do - let!(:conflict_content_item) { - FactoryGirl.create( - hash[:factory], - content_id: content_item.content_id, - locale: "fr", - ) - } - let(:state_name) { hash[:state] } - let(:expected_error) do - "state=#{hash[:state]} and locale=fr for content " + - "item=#{content_item.content_id} conflicts with content item " + - "id=#{conflict_content_item.id}" - end - - before do - content_item.locale = "fr" - validate - end - - it "adds the error to the base attribute" do - expect(content_item.errors[:base]).to eq([expected_error]) - end - end - end - - context "when state is superseded" do - let!(:conflict_content_item) { - FactoryGirl.create( - :superseded_content_item, - content_id: content_item.content_id, - locale: "fr", - ) - } - let(:state_name) { "superseded" } - - before { content_item.locale = "fr" } - - it { is_expected.to be_nil } - end - end - end -end diff --git a/spec/validators/version_for_document_validator_spec.rb b/spec/validators/version_for_document_validator_spec.rb new file mode 100644 index 000000000..43523db73 --- /dev/null +++ b/spec/validators/version_for_document_validator_spec.rb @@ -0,0 +1,52 @@ +require "rails_helper" + +RSpec.describe VersionForDocumentValidator do + let(:version) { 5 } + let(:document) { FactoryGirl.create(:document) } + + let(:edition) do + FactoryGirl.build(:edition, + document: document, + user_facing_version: version, + ) + end + + describe "#validate" do + subject(:validate) { described_class.new.validate(edition) } + + context "when it's missing a edition" do + it { is_expected.to be_nil } + end + + context "when document is nil" do + before { edition.document_id = nil } + it { is_expected.to be_nil } + end + + context "when version number is nil" do + before { edition.user_facing_version = nil } + it { is_expected.to be_nil } + end + + context "when version and document are the same" do + let!(:conflict_edition) { + FactoryGirl.create(:edition, + document: document, + user_facing_version: version, + ) + } + let(:expected_error) do + "user_facing_version=#{version} and document=#{document.id} " + + "conflicts with edition id=#{conflict_edition.id}" + end + + before do + validate + end + + it "adds the error to the base attribute" do + expect(edition.errors[:base]).to eq([expected_error]) + end + end + end +end diff --git a/spec/validators/version_for_locale_validator_spec.rb b/spec/validators/version_for_locale_validator_spec.rb deleted file mode 100644 index beb4a68d3..000000000 --- a/spec/validators/version_for_locale_validator_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require "rails_helper" - -RSpec.describe VersionForLocaleValidator do - let(:version) { 5 } - let(:locale) { "en" } - - let(:content_item) do - FactoryGirl.create( - :content_item, - user_facing_version: version, - locale: locale, - ) - end - - describe "#validate" do - subject(:validate) { described_class.new.validate(content_item) } - - context "when it's missing a content item" do - it { is_expected.to be_nil } - end - - context "when locale is nil" do - before { content_item.locale = nil } - it { is_expected.to be_nil } - end - - context "when version number is nil" do - before { content_item.user_facing_version = nil } - it { is_expected.to be_nil } - end - - context "when version, locale and content_id are the same" do - let!(:conflict_content_item) { - FactoryGirl.create( - :content_item, - content_id: content_item.content_id, - locale: "fr", - user_facing_version: version, - ) - } - let(:expected_error) do - "user_facing_version=#{version} and locale=fr for content item=" + - "#{content_item.content_id} conflicts with content item " + - "id=#{conflict_content_item.id}" - end - before do - content_item.locale = "fr" - validate - end - - it "adds the error to the base attribute" do - expect(content_item.errors[:base]).to eq([expected_error]) - end - end - end -end diff --git a/spec/workers/dependency_resolution_worker_spec.rb b/spec/workers/dependency_resolution_worker_spec.rb index 145ea9ec3..24e137f8d 100644 --- a/spec/workers/dependency_resolution_worker_spec.rb +++ b/spec/workers/dependency_resolution_worker_spec.rb @@ -1,9 +1,10 @@ require "rails_helper" RSpec.describe DependencyResolutionWorker, :perform do - let(:live_content_item) { FactoryGirl.create(:live_content_item, locale: "en") } let(:content_id) { SecureRandom.uuid } let(:locale) { "en" } + let(:document) { FactoryGirl.create(:document, content_id: content_id, locale: locale) } + let(:live_edition) { FactoryGirl.create(:live_edition, document: document) } subject(:worker_perform) do described_class.new.perform( @@ -14,10 +15,10 @@ ) end - let(:content_item_dependee) { double(:content_item_dependent, call: []) } + let(:edition_dependee) { double(:edition_dependent, call: []) } let(:dependencies) do [ - [live_content_item.content_id, "en"], + [content_id, "en"], ] end @@ -26,12 +27,12 @@ allow_any_instance_of(Queries::ContentDependencies).to receive(:call).and_return(dependencies) end - it "finds the content item dependees" do + it "finds the edition dependees" do expect(Queries::ContentDependencies).to receive(:new).with( content_id: content_id, locale: locale, - state_fallback_order: %w[published unpublished], - ).and_return(content_item_dependee) + content_stores: %w[live], + ).and_return(edition_dependee) worker_perform end @@ -50,10 +51,9 @@ end context "with a draft version available" do - let!(:draft_content_item) do - FactoryGirl.create(:draft_content_item, - content_id: live_content_item.content_id, - locale: "en", + let!(:draft_edition) do + FactoryGirl.create(:draft_edition, + document: document, user_facing_version: 2, ) end @@ -62,7 +62,7 @@ expect(DownstreamLiveWorker).to receive(:perform_async_in_queue).with( anything, a_hash_including( - content_id: live_content_item.content_id, + content_id: content_id, locale: "en", ) ) @@ -78,7 +78,7 @@ expect(DownstreamDraftWorker).to receive(:perform_async_in_queue).with( anything, a_hash_including( - content_id: draft_content_item.content_id, + content_id: content_id, locale: "en", ) ) @@ -91,7 +91,7 @@ end end - context "when there are translations of a content item" do + context "when there are translations of an edition" do context "and locale is specified" do let(:dependencies) do [ diff --git a/spec/workers/downstream_discard_draft_worker_spec.rb b/spec/workers/downstream_discard_draft_worker_spec.rb index 5fc9eb660..f7586effe 100644 --- a/spec/workers/downstream_discard_draft_worker_spec.rb +++ b/spec/workers/downstream_discard_draft_worker_spec.rb @@ -3,10 +3,9 @@ RSpec.describe DownstreamDiscardDraftWorker do let(:base_path) { "/foo" } - let(:content_item) do - FactoryGirl.create(:draft_content_item, + let(:edition) do + FactoryGirl.create(:draft_edition, base_path: base_path, - locale: "en", title: "Draft", ) end @@ -14,7 +13,7 @@ let(:arguments) do { "base_path" => base_path, - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "locale" => "en", "payload_version" => 1, "update_dependencies" => true, @@ -23,7 +22,7 @@ end before do - content_item.destroy + edition.destroy stub_request(:put, %r{.*content-store.*/content/.*}) stub_request(:delete, %r{.*content-store.*/content/.*}) end @@ -72,21 +71,21 @@ end end - context "has a live content item with same base_path" do - let!(:live_content_item) do - FactoryGirl.create(:live_content_item, + context "has a live edition with same base_path" do + let!(:live_edition) do + FactoryGirl.create(:live_edition, base_path: base_path, - content_id: content_item.content_id, + document: edition.document, title: "live", ) end let(:live_content_item_arguments) do - arguments.merge("live_content_item_id" => live_content_item.id) + arguments.merge("live_content_item_id" => live_edition.id) end - it "adds the live content item to the draft content store" do + it "adds the live edition to the draft content store" do expect(Adapters::DraftContentStore).to receive(:put_content_item) - .with(base_path, a_hash_including(title: live_content_item.title)) + .with(base_path, a_hash_including(title: live_edition.title)) subject.perform(live_content_item_arguments) end @@ -96,21 +95,21 @@ end end - context "has a live content item with a different base_path" do - let(:live_content_item) do - FactoryGirl.create(:live_content_item, + context "has a live edition with a different base_path" do + let(:live_edition) do + FactoryGirl.create(:live_edition, base_path: "/bar", - content_id: content_item.content_id, + document: edition.document, title: "Live", ) end let(:live_content_item_arguments) do - arguments.merge("live_content_item_id" => live_content_item.id) + arguments.merge("live_content_item_id" => live_edition.id) end - it "adds the live content item to the draft content store" do + it "adds the live edition to the draft content store" do expect(Adapters::DraftContentStore).to receive(:put_content_item) - .with("/bar", a_hash_including(title: live_content_item.title)) + .with("/bar", a_hash_including(title: live_edition.title)) subject.perform(live_content_item_arguments) end @@ -121,7 +120,7 @@ end end - context "doesn't have a live content item" do + context "doesn't have a live edition" do it "doesn't add to live draft content store" do expect(Adapters::DraftContentStore).to_not receive(:put_content_item) subject.perform(arguments) @@ -149,15 +148,14 @@ end it "wont send to content store without a base_path" do - pathless = FactoryGirl.create( - :draft_content_item, + pathless = FactoryGirl.create(:draft_edition, base_path: nil, document_type: "contact", schema_name: "contact" ) expect(Adapters::DraftContentStore).to_not receive(:delete_content_item) subject.perform( - arguments.merge("content_id" => pathless.content_id, "base_path" => nil) + arguments.merge("content_id" => pathless.document.content_id, "base_path" => nil) ) end end @@ -179,14 +177,14 @@ end describe "conflict protection" do - let(:content_id) { content_item.content_id } + let(:content_id) { edition.content_id } let(:logger) { Sidekiq::Logging.logger } before do - FactoryGirl.create(:live_content_item, base_path: "/foo") + FactoryGirl.create(:live_edition, base_path: "/foo") end - it "doesn't delete content item from content store" do + it "doesn't delete edition from content store" do expect(Adapters::DraftContentStore).to_not receive(:delete_content_item) subject.perform(arguments) end diff --git a/spec/workers/downstream_draft_worker_spec.rb b/spec/workers/downstream_draft_worker_spec.rb index f02a102be..c0a99cb7c 100644 --- a/spec/workers/downstream_draft_worker_spec.rb +++ b/spec/workers/downstream_draft_worker_spec.rb @@ -1,13 +1,13 @@ require "rails_helper" RSpec.describe DownstreamDraftWorker do - let(:content_item) do - FactoryGirl.create(:draft_content_item, base_path: "/foo", locale: "en") + let(:edition) do + FactoryGirl.create(:draft_edition, base_path: "/foo") end let(:base_arguments) do { - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "locale" => "en", "payload_version" => 1, "update_dependencies" => true, @@ -26,7 +26,7 @@ subject.perform(arguments.except("content_id")) }.to raise_error(KeyError) expect { - subject.perform(arguments.merge("content_item_id" => content_item.id)) + subject.perform(arguments.merge("content_item_id" => edition.id)) }.not_to raise_error end @@ -44,31 +44,30 @@ end describe "sends to draft content store" do - context "content item has a base path" do + context "edition has a base path" do it "sends put content to draft content store" do expect(Adapters::DraftContentStore).to receive(:put_content_item) subject.perform(arguments) end it "receives the base path" do - base_path = ContentItem.where(id: content_item.id).pluck(:base_path).first + base_path = Edition.where(id: edition.id).pluck(:base_path).first expect(Adapters::DraftContentStore).to receive(:put_content_item) .with(base_path, anything) subject.perform(arguments) end end - context "content item has a nil base path" do + context "edition has a nil base path" do it "doesn't send the item to the draft content store" do - pathless = FactoryGirl.create( - :draft_content_item, + pathless = FactoryGirl.create(:draft_edition, base_path: nil, document_type: "contact", schema_name: "contact", ) expect(Adapters::DraftContentStore).to_not receive(:put_content_item) - subject.perform(arguments.merge("content_id" => pathless.content_id)) + subject.perform(arguments.merge("content_id" => pathless.document.content_id)) end end end diff --git a/spec/workers/downstream_live_worker_spec.rb b/spec/workers/downstream_live_worker_spec.rb index cba0ae151..8e235c432 100644 --- a/spec/workers/downstream_live_worker_spec.rb +++ b/spec/workers/downstream_live_worker_spec.rb @@ -1,13 +1,13 @@ require "rails_helper" RSpec.describe DownstreamLiveWorker do - let(:content_item) do - FactoryGirl.create(:live_content_item, base_path: "/foo", locale: "en") + let(:edition) do + FactoryGirl.create(:live_edition, base_path: "/foo") end let(:base_arguments) do { - "content_id" => content_item.content_id, + "content_id" => edition.document.content_id, "locale" => "en", "payload_version" => 1, "message_queue_update_type" => "major", @@ -27,7 +27,7 @@ subject.perform(arguments.except("content_id")) }.to raise_error(KeyError) expect { - subject.perform(arguments.merge("content_item_id" => content_item.id)) + subject.perform(arguments.merge("content_item_id" => edition.id)) }.not_to raise_error end @@ -51,16 +51,16 @@ end describe "send to live content store" do - context "published content item" do + context "published edition" do it "sends content to live content store" do expect(Adapters::ContentStore).to receive(:put_content_item) subject.perform(arguments) end end - context "unpublished content item" do - let(:unpublished_content_item) { FactoryGirl.create(:unpublished_content_item) } - let(:unpublished_arguments) { arguments.merge(content_id: unpublished_content_item.content_id) } + context "unpublished edition" do + let(:unpublished_edition) { FactoryGirl.create(:unpublished_edition) } + let(:unpublished_arguments) { arguments.merge(content_id: unpublished_edition.document.content_id) } it "sends content to live content store" do expect(Adapters::ContentStore).to receive(:put_content_item) @@ -68,9 +68,9 @@ end end - context "superseded content item" do - let(:superseded_content_item) { FactoryGirl.create(:live_content_item, state: "superseded") } - let(:superseded_arguments) { arguments.merge(content_id: superseded_content_item.content_id) } + context "superseded edition" do + let(:superseded_edition) { FactoryGirl.create(:superseded_edition) } + let(:superseded_arguments) { arguments.merge(content_id: superseded_edition.document.content_id) } it "doesn't send to live content store" do expect(Adapters::ContentStore).to_not receive(:put_content_item) @@ -85,14 +85,13 @@ end it "wont send to content store without a base_path" do - pathless = FactoryGirl.create( - :live_content_item, + pathless = FactoryGirl.create(:live_edition, base_path: nil, document_type: "contact", schema_name: "contact" ) expect(Adapters::ContentStore).to_not receive(:put_content_item) - subject.perform(arguments.merge("content_id" => pathless.content_id)) + subject.perform(arguments.merge("content_id" => pathless.document.content_id)) end end @@ -128,23 +127,23 @@ end describe "draft-to-live protection" do - it "rejects draft content items" do - draft = FactoryGirl.create(:draft_content_item) + it "rejects draft editions" do + draft = FactoryGirl.create(:draft_edition) expect(Airbrake).to receive(:notify) .with(an_instance_of(AbortWorkerError), a_hash_including(:parameters)) - subject.perform(arguments.merge("content_id" => draft.content_id)) + subject.perform(arguments.merge("content_id" => draft.document.content_id)) end - it "allows live content items" do - live = FactoryGirl.create(:live_content_item) + it "allows live editions" do + live = FactoryGirl.create(:live_edition) expect(Airbrake).to_not receive(:notify) - subject.perform(arguments.merge("content_id" => live.content_id)) + subject.perform(arguments.merge("content_id" => live.document.content_id)) end end - describe "no content item" do + describe "no edition" do it "swallows the error" do expect(Airbrake).to receive(:notify) .with(an_instance_of(AbortWorkerError), a_hash_including(:parameters))