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 == "" || platform == PlatformIPhone || platform == PlatformIPad || platform == PlatformMacOS) &&
(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 versions when redownload
// The bag's updateProduct can serve pinned iOS and macOS 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
65 changes: 62 additions & 3 deletions pkg/appstore/appstore_update_product_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ var _ = Describe("AppStore (Update Product)", func() {
Return(http.Result[bagResult]{StatusCode: gohttp.StatusOK, Data: bag}, nil)
}

DescribeTable("retries once with the same pinned iOS request",
DescribeTable("retries once with the same pinned request",
func(versionID string, platform Platform) {
previous := expectPrimary()
if versionID == "" {
Expand Down Expand Up @@ -115,6 +115,7 @@ var _ = Describe("AppStore (Update Product)", func() {
Entry("latest iPhone", "", PlatformIPhone),
Entry("latest iPad", "", PlatformIPad),
Entry("explicit historical version", testVersionID, PlatformIPhone),
Entry("explicit macOS version", testVersionID, PlatformMacOS),
Entry("explicit version without a platform", testVersionID, Platform("")),
)

Expand Down Expand Up @@ -157,6 +158,66 @@ var _ = Describe("AppStore (Update Product)", func() {
Entry("update also unavailable", false, true),
)

DescribeTable("keeps TestFlight's Mac offer pinned through update fallback",
func(versionID string, emptyRedownload bool, updateUnavailable bool) {
app = App{ID: 899247664, BundleID: "com.apple.TestFlight"}
expected.Data.Items[0].URL = "https://example.com/TestFlight.pkg"
expected.Data.Items[0].Metadata["itemId"] = uint64(app.ID)
expected.Data.Items[0].Metadata["softwareVersionBundleId"] = app.BundleID
expected.Data.Items[0].Metadata["software-platform"] = "macos"
if versionID == "" {
pages := http.NewMockClient[[]byte](ctrl)
store.storefrontClient = pages
pages.EXPECT().Send(gomock.Any()).Do(func(req http.Request) {
Expect(req.URL).To(Equal("https://apps.apple.com/us/app/id899247664?platform=mac"))
}).Return(http.Result[[]byte]{StatusCode: gohttp.StatusOK, Data: macVersionPage(
`{"purchaseConfiguration":{"bundleId":"com.apple.TestFlight","appPlatforms":["mac"],"buyParams":"salableAdamId=899247664&appExtVrsId=123456789"}}`,
)}, nil)
}
unavailable := http.Result[downloadResult]{StatusCode: gohttp.StatusOK,
Data: downloadResult{CustomerMessage: "“TestFlight” No Longer Available"}}
redownload := unavailable
var requestErr error
if emptyRedownload {
redownload = http.Result[downloadResult]{}
requestErr = redownloadErr
}
update := expected
if updateUnavailable {
update = unavailable
}
var redownloadRequest http.Request
gomock.InOrder(
mockDownloadClient.EXPECT().Send(gomock.Any()).Do(func(req http.Request) {
Expect(req.Payload.(*http.XMLPayload).Content).To(HaveKeyWithValue("externalVersionId", testVersionID))
}).Return(unavailable, 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))
redownloadRequest = req
}).Return(redownload, requestErr),
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, platform, err := store.sendDownloadProduct(account, app, testGUID, versionID, PlatformMacOS)
Expect(platform).To(Equal(PlatformMacOS))
Expect(actual).To(Equal(update))
if updateUnavailable {
Expect(err).To(MatchError(ContainSubstring(unavailable.Data.CustomerMessage)))
} else {
Expect(err).ToNot(HaveOccurred())
}
},
Entry("latest Mac offer after availability errors", "", false, false),
Entry("explicit Mac version after availability errors", testVersionID, false, false),
Entry("latest Mac offer after empty HTTP 500", "", true, false),
Entry("update also unavailable", "", false, true),
)

DescribeTable("preserves availability responses when update is ineligible",
func(platform Platform, updateEndpoint, failureType, message string, status int) {
bag.URLBag.UpdateEndpoint = updateEndpoint
Expand All @@ -170,7 +231,6 @@ var _ = Describe("AppStore (Update Product)", func() {
Expect(actual).To(Equal(response))
},
Entry("missing endpoint", PlatformIPhone, "", "", "“Messenger” No Longer Available", 200),
Entry("macOS", PlatformMacOS, testUpdateEndpoint, "", "“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),
Expand Down Expand Up @@ -241,7 +301,6 @@ var _ = Describe("AppStore (Update Product)", func() {
Expect(errors.Is(err, redownloadErr)).To(BeTrue())
Expect(resolvedPlatform).To(Equal(platform))
},
Entry("pinned macOS", testVersionID, PlatformMacOS),
Entry("pinned tvOS", testVersionID, PlatformAppleTV),
Entry("pinned visionOS", testVersionID, PlatformVisionOS),
)
Expand Down
Loading