From 774fb8b8183853f0fef82afbd711da0a78133058 Mon Sep 17 00:00:00 2001 From: Harriet H-W Date: Wed, 18 Sep 2024 11:46:38 +0100 Subject: [PATCH 1/4] add Sidekiq Admin permissions to Test User --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index 858ad590fc8..186814f422d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,7 +7,7 @@ gds_organisation_id = "af07d5a5-df63-4ddc-9383-6a666845ebe9" User.create!( name: "Test user", - permissions: ["signin", "GDS Admin", "GDS Editor", "Managing Editor", "Export data"], + permissions: ["signin", "GDS Admin", "GDS Editor", "Managing Editor", "Export data", "Sidekiq Admin"], organisation_content_id: gds_organisation_id, organisation_slug: "government-digital-service", ) From f05cf161dfce2501905b554606d95b4bf1730f62 Mon Sep 17 00:00:00 2001 From: Harriet H-W Date: Wed, 18 Sep 2024 15:17:43 +0100 Subject: [PATCH 2/4] move workers unit tests to correct app folder --- .../unit/{ => app}/workers/schedule_publishing_worker_test.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/engines/content_object_store/test/unit/{ => app}/workers/schedule_publishing_worker_test.rb (100%) diff --git a/lib/engines/content_object_store/test/unit/workers/schedule_publishing_worker_test.rb b/lib/engines/content_object_store/test/unit/app/workers/schedule_publishing_worker_test.rb similarity index 100% rename from lib/engines/content_object_store/test/unit/workers/schedule_publishing_worker_test.rb rename to lib/engines/content_object_store/test/unit/app/workers/schedule_publishing_worker_test.rb From 2c8cc52189f834b229c919a78a705d0474586c2b Mon Sep 17 00:00:00 2001 From: Harriet H-W Date: Wed, 18 Sep 2024 15:49:56 +0100 Subject: [PATCH 3/4] add a Publish Intent Worker We will shortly use this to add publishing intents for Documents that have embedded content in them, when the embedded content is updated and we do not want to wait the default 5 mins for the cache to update. --- .../content_object_store/publish_intent_worker.rb | 14 ++++++++++++++ .../unit/app/workers/publish_intent_worker_test.rb | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 lib/engines/content_object_store/app/workers/content_object_store/publish_intent_worker.rb create mode 100644 lib/engines/content_object_store/test/unit/app/workers/publish_intent_worker_test.rb diff --git a/lib/engines/content_object_store/app/workers/content_object_store/publish_intent_worker.rb b/lib/engines/content_object_store/app/workers/content_object_store/publish_intent_worker.rb new file mode 100644 index 00000000000..a727fac801d --- /dev/null +++ b/lib/engines/content_object_store/app/workers/content_object_store/publish_intent_worker.rb @@ -0,0 +1,14 @@ +require "sidekiq/api" + +module ContentObjectStore + class PublishIntentWorker < WorkerBase + sidekiq_options queue: :content_block_publishing + + def perform(base_path, publish_timestamp) + publish_timestamp = Time.zone.parse(publish_timestamp) + publish_intent = PublishingApi::PublishIntentPresenter.new(base_path, publish_timestamp) + + Services.publishing_api.put_intent(base_path, publish_intent.as_json) + end + end +end diff --git a/lib/engines/content_object_store/test/unit/app/workers/publish_intent_worker_test.rb b/lib/engines/content_object_store/test/unit/app/workers/publish_intent_worker_test.rb new file mode 100644 index 00000000000..d97abd3d2cc --- /dev/null +++ b/lib/engines/content_object_store/test/unit/app/workers/publish_intent_worker_test.rb @@ -0,0 +1,13 @@ +require "test_helper" + +class ContentObjectStore::PublishIntentWorkerTest < ActiveSupport::TestCase + test "#perform adds a publishing intent to the Publishing API" do + base_path = "/base-path" + timestamp = Time.zone.now.to_s + publish_intent = { foo: "bar" } + + PublishingApi::PublishIntentPresenter.expects(:new).with(base_path, timestamp).once.returns(publish_intent) + Services.publishing_api.expects(:put_intent).once.with(base_path, publish_intent.as_json) + ContentObjectStore::PublishIntentWorker.new.perform(base_path, timestamp) + end +end From c5b936033a6e97ce87adb5d02665a9ea571c9ce6 Mon Sep 17 00:00:00 2001 From: Harriet H-W Date: Wed, 18 Sep 2024 11:50:55 +0100 Subject: [PATCH 4/4] Remove cache for host documents when block is updated/published When a content block is updated we do not want to wait the default cache time of 5 mins in order to see the changes in Documents that use this content block. Publish intents allow us to reset the cache time https://docs.publishing.service.gov.uk/repos/content-store/publish_intents.html --- .../lib/content_object_store/publishable.rb | 10 + .../content_block/workflow_test.rb | 181 ++++++++++++++++++ .../services/publish_edition_service_test.rb | 32 ++++ .../services/update_edition_service_test.rb | 41 ++++ 4 files changed, 264 insertions(+) diff --git a/lib/engines/content_object_store/app/lib/content_object_store/publishable.rb b/lib/engines/content_object_store/app/lib/content_object_store/publishable.rb index a597bc4b921..543e5a1be84 100644 --- a/lib/engines/content_object_store/app/lib/content_object_store/publishable.rb +++ b/lib/engines/content_object_store/app/lib/content_object_store/publishable.rb @@ -24,6 +24,7 @@ def publish_with_rollback(schema:, title:, details:) publish_publishing_api_edition(content_id:) update_content_block_document_with_live_edition(content_block_edition) content_block_edition.public_send(:publish!) + remove_cache_for_host_content(content_block_edition:) rescue PublishingFailureError => e discard_publishing_api_edition(content_id:) raise e @@ -51,6 +52,15 @@ def update_content_block_document(new_content_block_edition:, update_document_pa private + def remove_cache_for_host_content(content_block_edition:) + host_content_items = ContentObjectStore::GetHostContentItems.by_embedded_document( + content_block_document: content_block_edition.document, + ) + host_content_items.each do |host_content_item| + ContentObjectStore::PublishIntentWorker.perform_async(host_content_item.base_path, Time.zone.now.to_s) + end + end + def create_publishing_api_edition(content_id:, schema_id:, title:, details:, links:) Services.publishing_api.put_content(content_id, { schema_name: schema_id, diff --git a/lib/engines/content_object_store/test/integration/content_block/workflow_test.rb b/lib/engines/content_object_store/test/integration/content_block/workflow_test.rb index 77bb76bffe2..b8c68f2d31b 100644 --- a/lib/engines/content_object_store/test/integration/content_block/workflow_test.rb +++ b/lib/engines/content_object_store/test/integration/content_block/workflow_test.rb @@ -53,6 +53,14 @@ class ContentObjectStore::ContentBlock::WorkflowTest < ActionDispatch::Integrati "major", ] + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { + "total" => 0, + "results" => [], + }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [@content_id] + Services.stub :publishing_api, publishing_api_mock do post content_object_store.publish_content_object_store_content_block_edition_path(id: edition.id), params: { id: edition.id, @@ -67,6 +75,86 @@ class ContentObjectStore::ContentBlock::WorkflowTest < ActionDispatch::Integrati end end + test "#publish creates publish intents for all host documents" do + details = { + foo: "Foo text", + bar: "Bar text", + } + + organisation = create(:organisation) + document = create(:content_block_document, :email_address, content_id: @content_id, title: "Some Title") + edition = create(:content_block_edition, document:, details:, organisation:) + + fake_put_content_response = GdsApi::Response.new( + stub("http_response", code: 200, body: {}), + ) + fake_publish_content_response = GdsApi::Response.new( + stub("http_response", code: 200, body: {}), + ) + + publishing_api_mock = Minitest::Mock.new + publishing_api_mock.expect :put_content, fake_put_content_response, [ + @content_id, + { + schema_name: "content_block_type", + document_type: "content_block_type", + publishing_app: "whitehall", + title: "Some Title", + details: { + "foo" => "Foo text", + "bar" => "Bar text", + }, + links: { + primary_publishing_organisation: [organisation.content_id], + }, + }, + ] + publishing_api_mock.expect :publish, fake_publish_content_response, [ + @content_id, + "major", + ] + + host_content = + [ + { + "title" => "Content title", + "document_type" => "document", + "base_path" => "/host-document", + "content_id" => "1234abc", + "primary_publishing_organisation" => { + "content_id" => "456abc", + "title" => "Organisation", + "base_path" => "/organisation/org", + }, + }, + ] + + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { + "total" => 1, + "results" => host_content, + }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [@content_id] + + publishing_api_mock.expect :put_intent, {}, ["/host-document", + { + publish_time: Time.zone.now, + publishing_app: "whitehall", + rendering_app: "government-frontend", + routes: [{ path: "/host-document", type: "exact" }], + }] + + Sidekiq::Testing.inline! do + Services.stub :publishing_api, publishing_api_mock do + post content_object_store.publish_content_object_store_content_block_edition_path(id: edition.id), params: { + id: edition.id, + } + publishing_api_mock.verify + end + end + end + test "#update schedules the publication of an edition" do organisation = create(:organisation) @@ -145,6 +233,99 @@ class ContentObjectStore::ContentBlock::WorkflowTest < ActionDispatch::Integrati assert_not_equal first_job_id, second_job_id end end + + test "#update creates publish intents for host content" do + details = { + foo: "Foo text", + bar: "Bar text", + } + + organisation = create(:organisation) + document = create(:content_block_document, :email_address, content_id: @content_id, title: "Some Title") + edition = create(:content_block_edition, document:, details:, organisation:) + + fake_put_content_response = GdsApi::Response.new( + stub("http_response", code: 200, body: {}), + ) + fake_publish_content_response = GdsApi::Response.new( + stub("http_response", code: 200, body: {}), + ) + + publishing_api_mock = Minitest::Mock.new + publishing_api_mock.expect :put_content, fake_put_content_response, [ + @content_id, + { + schema_name: "content_block_type", + document_type: "content_block_type", + publishing_app: "whitehall", + title: "Another email", + details: { + "foo" => "newnew@example.com", + "bar" => "edited", + }, + links: { + primary_publishing_organisation: [organisation.content_id], + }, + }, + ] + publishing_api_mock.expect :publish, fake_publish_content_response, [ + @content_id, + "major", + ] + + host_content = + [ + { + "title" => "Content title", + "document_type" => "document", + "base_path" => "/host-document", + "content_id" => "1234abc", + "primary_publishing_organisation" => { + "content_id" => "456abc", + "title" => "Organisation", + "base_path" => "/organisation/org", + }, + }, + ] + + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { + "total" => 1, + "results" => host_content, + }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [@content_id] + + publishing_api_mock.expect :put_intent, {}, ["/host-document", + { + publish_time: Time.zone.now, + publishing_app: "whitehall", + rendering_app: "government-frontend", + routes: [{ path: "/host-document", type: "exact" }], + }] + + Sidekiq::Testing.inline! do + Services.stub :publishing_api, publishing_api_mock do + patch content_object_store.content_object_store_content_block_edition_path(edition), params: { + id: edition.id, + schedule_publishing: "schedule", + scheduled_at: { + "scheduled_publication(3i)": "2", + "scheduled_publication(2i)": "9", + "scheduled_publication(1i)": "2024", + "scheduled_publication(4i)": "10", + "scheduled_publication(5i)": "05", + }, + "content_block/edition": { + creator: "1", + details: { foo: "newnew@example.com", bar: "edited" }, + document_attributes: { block_type: "email_address", title: "Another email" }, + organisation_id: organisation.id, + }, + } + end + end + end end def stub_request_for_schema(block_type) diff --git a/lib/engines/content_object_store/test/unit/app/services/publish_edition_service_test.rb b/lib/engines/content_object_store/test/unit/app/services/publish_edition_service_test.rb index 28272585b34..d59262266d3 100644 --- a/lib/engines/content_object_store/test/unit/app/services/publish_edition_service_test.rb +++ b/lib/engines/content_object_store/test/unit/app/services/publish_edition_service_test.rb @@ -13,6 +13,8 @@ class ContentObjectStore::PublishEditionServiceTest < ActiveSupport::TestCase ContentObjectStore::ContentBlock::Schema.stubs(:find_by_block_type) .returns(schema) @organisation = create(:organisation) + + stub_publishing_api_has_embedded_content(content_id:, total: 0, results: []) end test "returns a ContentBlockEdition" do @@ -57,6 +59,13 @@ class ContentObjectStore::PublishEditionServiceTest < ActiveSupport::TestCase "major", ] + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { "content_id" => "1234abc", + "total" => 0, + "results" => [] }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [content_id] + Services.stub :publishing_api, publishing_api_mock do ContentObjectStore::PublishEditionService.new(schema).call(edition) @@ -66,6 +75,29 @@ class ContentObjectStore::PublishEditionServiceTest < ActiveSupport::TestCase end end + test "it queues publishing intents for host content on the Publishing API" do + host_content = + [ + { + "title" => "Content title", + "document_type" => "document", + "base_path" => "/host-document", + "content_id" => "1234abc", + "primary_publishing_organisation" => { + "content_id" => "456abc", + "title" => "Organisation", + "base_path" => "/organisation/org", + }, + }, + ] + + stub_publishing_api_has_embedded_content(content_id:, total: 0, results: host_content) + + ContentObjectStore::PublishIntentWorker.expects(:perform_async).with("/host-document", Time.zone.now.to_s).once + + ContentObjectStore::PublishEditionService.new(schema).call(edition) + end + test "if the publishing API request fails, the Whitehall ContentBlockEdition and ContentBlockDocument are rolled back" do exception = GdsApi::HTTPErrorResponse.new( 422, diff --git a/lib/engines/content_object_store/test/unit/app/services/update_edition_service_test.rb b/lib/engines/content_object_store/test/unit/app/services/update_edition_service_test.rb index 7b5a89aa577..81bb2b24023 100644 --- a/lib/engines/content_object_store/test/unit/app/services/update_edition_service_test.rb +++ b/lib/engines/content_object_store/test/unit/app/services/update_edition_service_test.rb @@ -8,6 +8,8 @@ class ContentObjectStore::UpdateEditionServiceTest < ActiveSupport::TestCase document: create(:content_block_document, :email_address, content_id:), details: { "foo" => "Foo text", "bar" => "Bar text" }, organisation: create(:organisation)) + + stub_publishing_api_has_embedded_content(content_id:, total: 0, results: []) end describe "#call" do @@ -125,6 +127,13 @@ class ContentObjectStore::UpdateEditionServiceTest < ActiveSupport::TestCase "major", ] + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { "content_id" => "1234abc", + "total" => 0, + "results" => [] }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [content_id] + Services.stub :publishing_api, publishing_api_mock do ContentObjectStore::UpdateEditionService .new(schema, @original_content_block_edition) @@ -218,6 +227,13 @@ class ContentObjectStore::UpdateEditionServiceTest < ActiveSupport::TestCase "major", ] + fake_embedded_content_response = GdsApi::Response.new(stub("http_response", + code: 200, body: { "content_id" => "1234abc", + "total" => 0, + "results" => [] }.to_json)) + + publishing_api_mock.expect :get_content_by_embedded_document, fake_embedded_content_response, [content_id] + Services.stub :publishing_api, publishing_api_mock do ContentObjectStore::UpdateEditionService .new(schema, @original_content_block_edition) @@ -227,6 +243,31 @@ class ContentObjectStore::UpdateEditionServiceTest < ActiveSupport::TestCase end end + test "it queues publishing intents for dependent content" do + dependent_content = + [ + { + "title" => "Content title", + "document_type" => "document", + "base_path" => "/host-document", + "content_id" => "1234abc", + "primary_publishing_organisation" => { + "content_id" => "456abc", + "title" => "Organisation", + "base_path" => "/organisation/org", + }, + }, + ] + + stub_publishing_api_has_embedded_content(content_id:, total: 0, results: dependent_content) + + ContentObjectStore::PublishIntentWorker.expects(:perform_async).with("/host-document", Time.zone.now.to_s).once + + ContentObjectStore::UpdateEditionService + .new(schema, @original_content_block_edition) + .call(edition_params) + end + test "if the publishing API request fails, the Whitehall ContentBlockEdition and ContentBlockDocument are rolled back" do exception = GdsApi::HTTPErrorResponse.new( 422,