Skip to content

Commit 0d45114

Browse files
authored
Verify episode publishing state (#1377)
* Guard for remote publishing state * Refactor * Fixup return * Fixup coment * Remove dupe logging * Fixup merge
1 parent 65dcab8 commit 0d45114

File tree

2 files changed

+131
-28
lines changed

2 files changed

+131
-28
lines changed

app/models/apple/publisher.rb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,47 @@ def mark_as_uploaded!(eps)
397397
end
398398
end
399399

400+
def verify_publishing_state!(eps)
401+
# Capture initial state before polling to detect drift
402+
initial_states = eps.to_h { |ep| [ep.feeder_id, ep.publishing_state] }
403+
404+
poll_episodes!(eps)
405+
406+
drifted_count = eps.count do |ep|
407+
initial_state = initial_states[ep.feeder_id]
408+
(ep.publishing_state != initial_state).tap do |drifted|
409+
if drifted
410+
Rails.logger.warn("Episode publishing state found to be out of sync", {
411+
episode_id: ep.feeder_id,
412+
local_expected_state: initial_state,
413+
remote_actual_state: ep.publishing_state
414+
})
415+
end
416+
end
417+
end
418+
419+
if drifted_count.positive?
420+
raise Apple::RetryPublishingError.new("Detected #{drifted_count} episodes with publishing state drift")
421+
end
422+
423+
true
424+
end
425+
400426
def publish_drafting!(eps)
401427
Rails.logger.tagged("##{__method__}") do
402-
eps = eps.select { |ep| ep.drafting? && ep.container_upload_complete? }
428+
# Guard: verify all episodes are in a consistent local and remote state
429+
verify_publishing_state!(eps)
430+
431+
drafting_eps, non_ready_eps = eps.partition { |ep| ep.drafting? && ep.container_upload_complete? }
432+
non_ready_eps.each do |ep|
433+
Rails.logger.info("Skipping publish for non-ready episode", {episode_id: ep.feeder_id,
434+
publishing_state: ep.publishing_state,
435+
container_upload_complete: ep.container_upload_complete?})
436+
end
437+
438+
Rails.logger.info("Publishing #{drafting_eps.length} drafting episodes.", {drafting_episode_ids: drafting_eps.map(&:feeder_id)}) if drafting_eps.any?
403439

404-
res = Apple::Episode.publish(api, show, eps)
440+
res = Apple::Episode.publish(api, show, drafting_eps)
405441

406442
Rails.logger.info("Published #{res.length} drafting episodes.")
407443
end

test/models/apple/publisher_test.rb

Lines changed: 93 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,57 @@
398398
end
399399
end
400400

401+
describe "#verify_publishing_state!" do
402+
let(:episode1) { build(:uploaded_apple_episode, show: apple_publisher.show, api_response: build(:apple_episode_api_response, publishing_state: "DRAFTING")) }
403+
let(:episode2) { build(:uploaded_apple_episode, show: apple_publisher.show, api_response: build(:apple_episode_api_response, publishing_state: "DRAFTING")) }
404+
let(:episodes) { [episode1, episode2] }
405+
406+
it "should not raise error when episodes remain in same state" do
407+
apple_publisher.stub(:poll_episodes!, nil) do
408+
result = apple_publisher.verify_publishing_state!(episodes)
409+
assert result == true
410+
end
411+
end
412+
413+
it "should raise RetryPublishingError when state drift is detected" do
414+
# Simulate state change during poll - episode1 starts DRAFTING but becomes PUBLISHED after poll
415+
apple_publisher.stub(:poll_episodes!, proc {
416+
episode1.api_response["api_response"]["val"]["data"]["attributes"]["publishingState"] = "PUBLISHED"
417+
}) do
418+
error = assert_raises(Apple::RetryPublishingError) do
419+
apple_publisher.verify_publishing_state!(episodes)
420+
end
421+
assert_match(/Detected 1 episodes with publishing state drift/, error.message)
422+
end
423+
end
424+
425+
it "should detect drift and raise error even with non-DRAFTING episodes" do
426+
# episode1 starts in PUBLISHED state, then drifts to ARCHIVED
427+
episode1.api_response["api_response"]["val"]["data"]["attributes"]["publishingState"] = "PUBLISHED"
428+
429+
apple_publisher.stub(:poll_episodes!, proc {
430+
episode1.api_response["api_response"]["val"]["data"]["attributes"]["publishingState"] = "ARCHIVED"
431+
}) do
432+
error = assert_raises(Apple::RetryPublishingError) do
433+
apple_publisher.verify_publishing_state!(episodes)
434+
end
435+
assert_match(/Detected 1 episodes with publishing state drift/, error.message)
436+
end
437+
end
438+
439+
it "should raise error with count when multiple episodes drift" do
440+
apple_publisher.stub(:poll_episodes!, proc {
441+
episode1.api_response["api_response"]["val"]["data"]["attributes"]["publishingState"] = "PUBLISHED"
442+
episode2.api_response["api_response"]["val"]["data"]["attributes"]["publishingState"] = "ARCHIVED"
443+
}) do
444+
error = assert_raises(Apple::RetryPublishingError) do
445+
apple_publisher.verify_publishing_state!(episodes)
446+
end
447+
assert_match(/Detected 2 episodes with publishing state drift/, error.message)
448+
end
449+
end
450+
end
451+
401452
describe "#publish_drafting!" do
402453
let(:episode1) { build(:uploaded_apple_episode, show: apple_publisher.show, api_response: build(:apple_episode_api_response, publishing_state: "DRAFTING")) }
403454
let(:episode2) { build(:uploaded_apple_episode, show: apple_publisher.show, api_response: build(:apple_episode_api_response, publishing_state: "DRAFTING")) }
@@ -407,8 +458,10 @@
407458
mock = Minitest::Mock.new
408459
mock.expect(:call, [], [Apple::Api, Apple::Show, episodes])
409460

410-
Apple::Episode.stub(:publish, mock) do
411-
apple_publisher.publish_drafting!(episodes)
461+
apple_publisher.stub(:verify_publishing_state!, nil) do
462+
Apple::Episode.stub(:publish, mock) do
463+
apple_publisher.publish_drafting!(episodes)
464+
end
412465
end
413466

414467
assert mock.verify
@@ -656,7 +709,9 @@
656709

657710
apple_publisher.stub(:upload_media!, upload_mock) do
658711
apple_publisher.stub(:process_delivery!, ->(*) {}) do
659-
apple_publisher.upload_and_process!([episode])
712+
apple_publisher.stub(:verify_publishing_state!, nil) do
713+
apple_publisher.upload_and_process!([episode])
714+
end
660715
end
661716
end
662717

@@ -670,7 +725,9 @@
670725
mock.expect(:call, nil, [[episode]])
671726
apple_publisher.stub(:upload_media!, mock) do
672727
apple_publisher.stub(:process_delivery!, ->(*) {}) do
673-
apple_publisher.upload_and_process!([episode])
728+
apple_publisher.stub(:verify_publishing_state!, nil) do
729+
apple_publisher.upload_and_process!([episode])
730+
end
674731
end
675732
end
676733

@@ -815,9 +872,11 @@
815872
apple_publisher.stub(:increment_asset_wait!, increment_mock) do
816873
apple_publisher.stub(:wait_for_upload_processing, wait_upload_mock) do
817874
apple_publisher.stub(:wait_for_asset_state, wait_for_asset_state_stub) do
818-
Apple::Episode.stub(:probe_asset_state, probe_mock) do
819-
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
820-
apple_publisher.process_delivery!([episode])
875+
apple_publisher.stub(:verify_publishing_state!, ->(*) {}) do
876+
Apple::Episode.stub(:probe_asset_state, probe_mock) do
877+
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
878+
apple_publisher.process_delivery!([episode])
879+
end
821880
end
822881
end
823882
end
@@ -896,10 +955,12 @@
896955
apple_publisher.stub(:wait_for_upload_processing, ->(*) {}) do
897956
apple_publisher.stub(:wait_for_asset_state, wait_for_asset_state_stub) do
898957
apple_publisher.stub(:check_for_stuck_episodes, ->(*) {}) do
899-
Apple::Episode.stub(:probe_asset_state, probe_mock) do
900-
apple_publisher.stub(:mark_as_delivered!, ->(*) {}) do
901-
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
902-
apple_publisher.process_delivery!([episode])
958+
apple_publisher.stub(:verify_publishing_state!, ->(*) {}) do
959+
Apple::Episode.stub(:probe_asset_state, probe_mock) do
960+
apple_publisher.stub(:mark_as_delivered!, ->(*) {}) do
961+
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
962+
apple_publisher.process_delivery!([episode])
963+
end
903964
end
904965
end
905966
end
@@ -938,10 +999,12 @@
938999
apple_publisher.stub(:increment_asset_wait!, ->(*) {}) do
9391000
apple_publisher.stub(:wait_for_upload_processing, ->(*) {}) do
9401001
apple_publisher.stub(:wait_for_asset_state, wait_for_asset_state_stub) do
941-
Apple::Episode.stub(:probe_asset_state, probe_mock) do
942-
apple_publisher.stub(:mark_as_delivered!, ->(*) {}) do
943-
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
944-
apple_publisher.process_delivery!(episodes)
1002+
apple_publisher.stub(:verify_publishing_state!, ->(*) {}) do
1003+
Apple::Episode.stub(:probe_asset_state, probe_mock) do
1004+
apple_publisher.stub(:mark_as_delivered!, ->(*) {}) do
1005+
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
1006+
apple_publisher.process_delivery!(episodes)
1007+
end
9451008
end
9461009
end
9471010
end
@@ -996,9 +1059,11 @@
9961059
apple_publisher.stub(:wait_for_upload_processing, ->(*) {}) do
9971060
apple_publisher.stub(:wait_for_asset_state, wait_for_asset_state_stub) do
9981061
apple_publisher.stub(:check_for_stuck_episodes, ->(*) {}) do
999-
Apple::Episode.stub(:probe_asset_state, probe_mock) do
1000-
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
1001-
apple_publisher.process_delivery!([episode1, episode2, episode3])
1062+
apple_publisher.stub(:verify_publishing_state!, ->(*) {}) do
1063+
Apple::Episode.stub(:probe_asset_state, probe_mock) do
1064+
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
1065+
apple_publisher.process_delivery!([episode1, episode2, episode3])
1066+
end
10021067
end
10031068
end
10041069
end
@@ -1047,15 +1112,17 @@
10471112
apple_publisher.stub(:wait_for_upload_processing, ->(*) {}) do
10481113
apple_publisher.stub(:wait_for_asset_state, wait_for_asset_state_stub) do
10491114
apple_publisher.stub(:check_for_stuck_episodes, ->(*) {}) do
1050-
Apple::Episode.stub(:probe_asset_state, probe_mock) do
1051-
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
1052-
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
1053-
# Should raise timeout error when timeout is reached
1054-
error = assert_raises(Apple::AssetStateTimeoutError) do
1055-
apple_publisher.process_delivery!([episode1, episode2])
1115+
apple_publisher.stub(:verify_publishing_state!, ->(*) {}) do
1116+
Apple::Episode.stub(:probe_asset_state, probe_mock) do
1117+
apple_publisher.stub(:mark_as_delivered!, mark_delivered_mock) do
1118+
apple_publisher.stub(:log_asset_wait_duration!, ->(*) {}) do
1119+
# Should raise timeout error when timeout is reached
1120+
error = assert_raises(Apple::AssetStateTimeoutError) do
1121+
apple_publisher.process_delivery!([episode1, episode2])
1122+
end
1123+
1124+
assert_equal 2, error.episodes.length, "Error should contain both episodes"
10561125
end
1057-
1058-
assert_equal 2, error.episodes.length, "Error should contain both episodes"
10591126
end
10601127
end
10611128
end

0 commit comments

Comments
 (0)