Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/appstore/appstore_download_product.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (t *appstore) sendDownloadProduct(acc Account, app App, guid, externalVersi

redownloadRes, err := t.downloadClient.Send(t.downloadProductRequest(redownload, acc, app, guid, externalVersionID))
if bag.UpdateEndpoint != "" && externalVersionID != "" &&
(platform == "" || platform == PlatformIPhone || platform == PlatformIPad || platform == PlatformMacOS) &&
(platform == "" || platform == PlatformIPhone || platform == PlatformIPad || platform == PlatformMacOS || platform == PlatformAppleTV) &&
(isEmptyRedownloadError(err) || (err == nil && isUnavailableDownloadProductResponse(redownloadRes))) {
updateRes, updateErr := t.sendUpdateProduct(bag.UpdateEndpoint, acc, app, guid, externalVersionID)

Expand Down Expand Up @@ -108,7 +108,7 @@ func (t *appstore) sendUpdateProduct(endpoint string, acc Account, app App, guid
return http.Result[downloadResult]{}, err
}

// The bag's updateProduct can serve pinned iOS and macOS versions when redownload
// The bag's updateProduct can serve pinned iOS, macOS, and tvOS versions when redownload
// returns an empty HTTP 500 or a message-only availability error. Keep
// the same session and version selection.
res, err := t.downloadClient.Send(t.downloadProductRequest(update, acc, app, guid, externalVersionID))
Expand Down
3 changes: 3 additions & 0 deletions pkg/appstore/appstore_download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,9 @@ var _ = Describe("AppStore (Download)", func() {
Entry("normal unspecified-platform behavior is preserved", Platform(""), "", "", "AppleTVOS", true),
Entry("an explicit tvOS version without a platform is preserved", Platform(""), "818970197", "", "AppleTVOS", true),
Entry("explicit tvOS downloads still work", PlatformAppleTV, "818970197", "", "AppleTVOS", true),
Entry("tvOS update accepts tvOS", PlatformAppleTV, "818970197", "update", "AppleTVOS", true),
Entry("tvOS update rejects iOS", PlatformAppleTV, "818970197", "update", "iPhoneOS", false),
Entry("tvOS update rejects visionOS", PlatformAppleTV, "818970197", "update", "XROS", false),
)

Describe("macOS packages", func() {
Expand Down
36 changes: 28 additions & 8 deletions pkg/appstore/appstore_update_product_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ var _ = Describe("AppStore (Update Product)", func() {
Entry("latest iPad", "", PlatformIPad),
Entry("explicit historical version", testVersionID, PlatformIPhone),
Entry("explicit macOS version", testVersionID, PlatformMacOS),
Entry("explicit tvOS version", testVersionID, PlatformAppleTV),
Entry("explicit version without a platform", testVersionID, Platform("")),
)

DescribeTable("tries update once for a message-only availability response",
func(primaryUnavailable, updateUnavailable bool) {
func(platform Platform, primaryUnavailable, updateUnavailable bool) {
unavailable := http.Result[downloadResult]{StatusCode: gohttp.StatusOK,
Data: downloadResult{CustomerMessage: "“Messenger” No Longer Available"}}
primary := http.Result[downloadResult]{StatusCode: gohttp.StatusOK}
Expand All @@ -132,30 +133,38 @@ var _ = Describe("AppStore (Update Product)", func() {
update = unavailable
}

var redownloadRequest http.Request
gomock.InOrder(
mockDownloadClient.EXPECT().Send(gomock.Any()).Return(primary, nil),
mockBagClient.EXPECT().Send(gomock.Any()).Return(http.Result[bagResult]{StatusCode: gohttp.StatusOK, Data: bag}, nil),
mockDownloadClient.EXPECT().Send(gomock.Any()).Do(func(req http.Request) {
Expect(req.URL).To(Equal(testRedownloadEndpoint + "?guid=" + testGUID))
Expect(req.Payload.(*http.XMLPayload).Content).To(HaveKeyWithValue("appExtVrsId", testVersionID))
redownloadRequest = req
}).Return(unavailable, nil),
mockDownloadClient.EXPECT().Send(gomock.Any()).Do(func(req http.Request) {
Expect(req.URL).To(Equal(testUpdateEndpoint + "?guid=" + testGUID))
Expect(req.Headers).To(Equal(redownloadRequest.Headers))
Expect(req.Payload).To(Equal(redownloadRequest.Payload))
Expect(req.Payload.(*http.XMLPayload).Content).To(HaveKeyWithValue("appExtVrsId", testVersionID))
}).Return(update, nil),
)

actual, _, err := store.sendDownloadProduct(account, app, testGUID, testVersionID, PlatformIPhone)
actual, resolvedPlatform, err := store.sendDownloadProduct(account, app, testGUID, testVersionID, platform)
Expect(resolvedPlatform).To(Equal(platform))
Expect(actual).To(Equal(update))
if updateUnavailable {
Expect(err).To(MatchError(ContainSubstring(unavailable.Data.CustomerMessage)))
} else {
Expect(err).ToNot(HaveOccurred())
}
},
Entry("redownload unavailable", false, false),
Entry("both initial endpoints unavailable", true, false),
Entry("update also unavailable", false, true),
Entry("redownload unavailable", PlatformIPhone, false, false),
Entry("both initial endpoints unavailable", PlatformIPhone, true, false),
Entry("update also unavailable", PlatformIPhone, false, true),
Entry("tvOS redownload unavailable", PlatformAppleTV, false, false),
Entry("tvOS both initial endpoints unavailable", PlatformAppleTV, true, false),
Entry("tvOS update also unavailable", PlatformAppleTV, false, true),
)

DescribeTable("keeps TestFlight's Mac offer pinned through update fallback",
Expand Down Expand Up @@ -231,7 +240,6 @@ var _ = Describe("AppStore (Update Product)", func() {
Expect(actual).To(Equal(response))
},
Entry("missing endpoint", PlatformIPhone, "", "", "“Messenger” No Longer Available", 200),
Entry("tvOS", PlatformAppleTV, testUpdateEndpoint, "", "“Messenger” No Longer Available", 200),
Entry("visionOS", PlatformVisionOS, testUpdateEndpoint, "", "“Messenger” No Longer Available", 200),
Entry("license failure", PlatformIPhone, testUpdateEndpoint, FailureTypeLicenseNotFound, "“Messenger” No Longer Available", 200),
Entry("other message", PlatformIPhone, testUpdateEndpoint, "", "Sign in required", 200),
Expand Down Expand Up @@ -291,7 +299,7 @@ var _ = Describe("AppStore (Update Product)", func() {
Expect(err).To(MatchError("invalid download endpoint in bag"))
})

DescribeTable("does not extend the fallback to unverified platforms",
DescribeTable("does not update unpinned tvOS or unverified platforms",
func(versionID string, platform Platform) {
previous := expectPrimary()
mockDownloadClient.EXPECT().Send(gomock.Any()).After(previous).
Expand All @@ -301,10 +309,22 @@ var _ = Describe("AppStore (Update Product)", func() {
Expect(errors.Is(err, redownloadErr)).To(BeTrue())
Expect(resolvedPlatform).To(Equal(platform))
},
Entry("pinned tvOS", testVersionID, PlatformAppleTV),
Entry("unpinned tvOS", "", PlatformAppleTV),
Entry("pinned visionOS", testVersionID, PlatformVisionOS),
)

It("preserves an unpinned tvOS availability response without updating", func() {
previous := expectPrimary()
response := http.Result[downloadResult]{StatusCode: gohttp.StatusOK,
Data: downloadResult{CustomerMessage: "No Longer Available"}}
mockDownloadClient.EXPECT().Send(gomock.Any()).After(previous).Return(response, nil)

actual, platform, err := store.sendDownloadProduct(account, app, testGUID, "", PlatformAppleTV)
Expect(err).ToNot(HaveOccurred())
Expect(actual).To(Equal(response))
Expect(platform).To(Equal(PlatformAppleTV))
})

It("propagates an update error without retrying again", func() {
previous := expectPrimary()
gomock.InOrder(
Expand Down
Loading