Skip to content

Commit

Permalink
Fix change build to retrigger submission if it was triggered before (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nid90 authored Nov 19, 2024
1 parent b9d10d5 commit 20dbbf2
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 6 deletions.
8 changes: 4 additions & 4 deletions app/models/app_store_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ class AppStoreSubmission < StoreSubmission
}
FINAL_STATES = %w[approved]
IMMUTABLE_STATES = %w[preparing approved submitting_for_review submitted_for_review cancelling]
PRE_PREPARE_STATES = %w[created preprocessing cancelled review_failed failed]
CHANGEABLE_STATES = %w[created preprocessing prepared cancelled review_failed failed approved]
PRE_PREPARE_STATES = %w[created cancelled review_failed failed]
CHANGEABLE_STATES = %w[created prepared failed_prepare cancelled review_failed failed approved]
CANCELABLE_STATES = %w[submitted_for_review]
STAMPABLE_REASONS = %w[
triggered
Expand Down Expand Up @@ -82,7 +82,7 @@ class AppStoreSubmission < StoreSubmission
state(*STATES.keys)

event :start_prepare, after_commit: :on_start_prepare! do
transitions to: :preparing
transitions from: CHANGEABLE_STATES, to: :preparing
end

event :finish_prepare, after_commit: :on_finish_prepare! do
Expand Down Expand Up @@ -153,7 +153,7 @@ def trigger!
end

def retrigger!
return unless created? || cancelled?
return if created?

reset_store_info!
trigger!
Expand Down
4 changes: 2 additions & 2 deletions app/models/play_store_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class PlayStoreSubmission < StoreSubmission
state(*STATES.keys)

event :preprocess do
transitions from: :created, to: :preprocessing
transitions from: CHANGEABLE_STATES, to: :preprocessing
end

event :start_prepare, after: :on_start_prepare! do
Expand Down Expand Up @@ -183,7 +183,7 @@ def attach_build(build)
end

def retrigger!
return unless created?
return if created?

reset_store_info!
trigger!
Expand Down
285 changes: 285 additions & 0 deletions spec/libs/coordinators/update_build_on_production_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
# frozen_string_literal: true

require "rails_helper"

describe Coordinators::UpdateBuildOnProduction do
context "when android" do
let(:app) { create(:app, :android) }
let(:config) {
{
workflows: {
internal: nil,
release_candidate: {
name: Faker::FunnyName.name,
id: Faker::Number.number(digits: 8),
artifact_name_pattern: nil,
kind: "release_candidate"
}
},
internal_release: nil,
beta_release: {
auto_promote: false,
submissions: [
{
number: 1,
submission_type: "PlayStoreSubmission",
submission_config: {id: Faker::FunnyName.name, name: Faker::FunnyName.name, is_internal: true},
integrable_id: app.id,
integrable_type: "App"
}
]
},
production_release: {
auto_promote: false,
submissions: [
{
number: 1,
submission_type: "PlayStoreSubmission",
submission_config: GooglePlayStoreIntegration::PROD_CHANNEL,
rollout_config: {enabled: true, stages: [1, 5, 10, 50, 100]},
integrable_id: app.id,
integrable_type: "App"
}
]
}
}
}
let(:release) { create(:release, :on_track, is_v2: true) }
let(:release_platform_run) { create(:release_platform_run, :on_track, release:) }
let(:workflow_run) { create(:workflow_run, :rc, release_platform_run:) }
let(:build) { create(:build, release_platform_run:, workflow_run:) }
let(:production_release) { create(:production_release, :inflight, release_platform_run:, build:) }
let(:store_submission) { create(:play_store_submission, :prepared, release_platform_run:, parent_release: production_release, build:) }

it "returns an error if the release is not actionable" do
release_platform_run.update!(status: "stopped")
expect {
described_class.call(store_submission, build.id)
}.to raise_error("production release is not actionable")
end

it "returns an error if the production release is not inflight" do
production_release.update!(status: "active")
expect {
described_class.call(store_submission, build.id)
}.to raise_error("production release is not editable")
end

it "returns an error if the build is not found" do
expect {
described_class.call(store_submission, "invalid_build_id")
}.to raise_error(ActiveRecord::RecordNotFound)
end

it "returns an error if the rc build is not found for the release platform run" do
new_workflow_run = create(:workflow_run, :internal, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to raise_error(ActiveRecord::RecordNotFound)
end

it "does nothing id the build is the same as the current build" do
expect {
described_class.call(store_submission, build.id)
}.not_to change(store_submission, :build_id)
end

it "attaches the new build to the production store submission" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(store_submission, :build_id).to(new_build.id)
end

it "updates the build number on the production release" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(production_release, :build_id).to(new_build.id)
end

it "does not update the build on the production release if attach build fails" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
store_submission.update!(status: "preparing")
described_class.call(store_submission, new_build.id)
expect(production_release.reload.build).to eq(build)
end

it "retriggers the store submission if the submission was previously triggered" do
allow(StoreSubmissions::PlayStore::UploadJob).to receive(:perform_later)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.preprocessing?).to be(true)
expect(StoreSubmissions::PlayStore::UploadJob).to have_received(:perform_later).with(store_submission.id).once
end

it "does not retrigger the store submission if attach build fails" do
allow(StoreSubmissions::PlayStore::UploadJob).to receive(:perform_later)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
store_submission.update!(status: "preparing")
described_class.call(store_submission, new_build.id)
expect(StoreSubmissions::PlayStore::UploadJob).not_to have_received(:perform_later).with(store_submission.id)
end

it "does not retrigger the store submission if the submission was not previously triggered" do
store_submission.update!(status: "created")
allow(StoreSubmissions::PlayStore::UploadJob).to receive(:perform_later)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.created?).to be(true)
expect(StoreSubmissions::PlayStore::UploadJob).not_to have_received(:perform_later).with(store_submission.id)
end
end

context "when ios" do
let(:app) { create(:app, :ios) }
let(:config) {
{
workflows: {
internal: nil,
release_candidate: {
name: Faker::FunnyName.name,
id: Faker::Number.number(digits: 8),
artifact_name_pattern: nil,
kind: "release_candidate"
}
},
internal_release: nil,
beta_release: {
auto_promote: false,
submissions: [
{
number: 1,
submission_type: "TestFlightSubmission",
submission_config: {id: Faker::FunnyName.name, name: Faker::FunnyName.name, is_internal: true},
integrable_id: app.id,
integrable_type: "App"
}
]
},
production_release: {
auto_promote: false,
submissions: [
{
number: 1,
submission_type: "AppStoreSubmission",
submission_config: AppStoreIntegration::PROD_CHANNEL,
rollout_config: {enabled: true, stages: AppStoreIntegration::DEFAULT_PHASED_RELEASE_SEQUENCE},
integrable_id: app.id,
integrable_type: "App"
}
]
}
}
}
let(:release) { create(:release, :on_track, is_v2: true) }
let(:release_platform_run) { create(:release_platform_run, :on_track, release:) }
let(:workflow_run) { create(:workflow_run, :rc, release_platform_run:) }
let(:build) { create(:build, release_platform_run:, workflow_run:) }
let(:production_release) { create(:production_release, :inflight, release_platform_run:, build:) }
let(:store_submission) { create(:app_store_submission, :prepared, release_platform_run:, parent_release: production_release, build:) }

it "returns an error if the release is not actionable" do
release_platform_run.update!(status: "stopped")
expect {
described_class.call(store_submission, build.id)
}.to raise_error("production release is not actionable")
end

it "returns an error if the production release is not inflight" do
production_release.update!(status: "active")
expect {
described_class.call(store_submission, build.id)
}.to raise_error("production release is not editable")
end

it "returns an error if the build is not found" do
expect {
described_class.call(store_submission, "invalid_build_id")
}.to raise_error(ActiveRecord::RecordNotFound)
end

it "returns an error if the rc build is not found for the release platform run" do
new_workflow_run = create(:workflow_run, :internal, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to raise_error(ActiveRecord::RecordNotFound)
end

it "does nothing id the build is the same as the current build" do
expect {
described_class.call(store_submission, build.id)
}.not_to change(store_submission, :build_id)
end

it "attaches the new build to the production store submission" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(store_submission, :build_id).to(new_build.id)
end

it "does not update the build on the production release if attach build fails" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
store_submission.update!(status: "preparing")
described_class.call(store_submission, new_build.id)
expect(production_release.reload.build).to eq(build)
end

it "updates the build on the production release if attach build succeeds" do
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
expect {
described_class.call(store_submission, new_build.id)
}.to change(production_release, :build_id).to(new_build.id)
end

it "does not retrigger the store submission if attach build fails" do
allow(StoreSubmissions::AppStore::FindBuildJob).to receive(:perform_async)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
store_submission.update!(status: "preparing")
described_class.call(store_submission, new_build.id)
expect(StoreSubmissions::AppStore::FindBuildJob).not_to have_received(:perform_async).with(store_submission.id)
end

it "retriggers the store submission if the submission was previously triggered" do
allow(StoreSubmissions::AppStore::FindBuildJob).to receive(:perform_async)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.preparing?).to be(true)
expect(StoreSubmissions::AppStore::FindBuildJob).to have_received(:perform_async).with(store_submission.id).once
end

it "retriggers the store submission if the submission was cancelled" do
store_submission.update!(status: "cancelled")
allow(StoreSubmissions::AppStore::FindBuildJob).to receive(:perform_async)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.preparing?).to be(true)
expect(StoreSubmissions::AppStore::FindBuildJob).to have_received(:perform_async).with(store_submission.id).once
end

it "does not retrigger the store submission if the submission was not previously triggered" do
store_submission.update!(status: "created")
allow(StoreSubmissions::AppStore::FindBuildJob).to receive(:perform_async)
new_workflow_run = create(:workflow_run, :rc, release_platform_run:)
new_build = create(:build, release_platform_run:, workflow_run: new_workflow_run)
described_class.call(store_submission, new_build.id)
expect(store_submission.reload.created?).to be(true)
expect(StoreSubmissions::AppStore::FindBuildJob).not_to have_received(:perform_async).with(store_submission.id)
end
end
end
20 changes: 20 additions & 0 deletions spec/models/app_store_submission_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,24 @@
expect(submission.reload.approved?).to be(true)
end
end

describe "#attach_build" do
let(:build) { create(:build) }
let(:submission) { create(:app_store_submission, :created, build: build) }

it "attaches the new build to the submission" do
new_build = create(:build)
submission.attach_build(new_build)
expect(submission.reload.build).to eq(new_build)
end

(described_class::STATES.values - described_class::CHANGEABLE_STATES).each do |state|
it "does not change the build if the submission is in #{state} state" do
submission.update!(status: state)
new_build = create(:build)
submission.attach_build(new_build)
expect(submission.reload.build).to eq(build)
end
end
end
end
21 changes: 21 additions & 0 deletions spec/models/play_store_submission_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,25 @@
expect(prev_rollout.reload.status).to eq("fully_released")
end
end

describe "#attach_build" do
let(:build) { create(:build) }
let(:production_release) { create(:production_release, :inflight, build:) }
let(:submission) { create(:play_store_submission, :created, build:, parent_release: production_release) }

it "attaches the new build to the submission" do
new_build = create(:build)
submission.attach_build(new_build)
expect(submission.reload.build).to eq(new_build)
end

(described_class::STATES.values - described_class::CHANGEABLE_STATES).each do |state|
it "does not change the build if the submission is in #{state} state" do
submission.update!(status: state)
new_build = create(:build)
submission.attach_build(new_build)
expect(submission.reload.build).to eq(build)
end
end
end
end

0 comments on commit 20dbbf2

Please sign in to comment.