Skip to content

Commit

Permalink
Implement appstore purchase logic
Browse files Browse the repository at this point in the history
  • Loading branch information
majd committed Dec 5, 2022
1 parent eb42a63 commit 6543934
Show file tree
Hide file tree
Showing 16 changed files with 638 additions and 93 deletions.
27 changes: 15 additions & 12 deletions pkg/appstore/appstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,17 @@ type AppStore interface {
Info() error
Revoke() error
Search(term, countryCode, deviceFamily string, limit int64) error
Purchase(bundleID, countryCode, deviceFamily string) error
}

type appstore struct {
keychain keychain.Keychain
loginClient http.Client[LoginResult]
searchClient http.Client[SearchResult]
ioReader io.Reader
machine util.Machine
logger log.Logger
keychain keychain.Keychain
loginClient http.Client[LoginResult]
searchClient http.Client[SearchResult]
purchaseClient http.Client[PurchaseResult]
ioReader io.Reader
machine util.Machine
logger log.Logger
}

type Args struct {
Expand All @@ -37,11 +39,12 @@ func NewAppStore(args *Args) AppStore {
}

return &appstore{
keychain: args.Keychain,
loginClient: http.NewClient[LoginResult](clientArgs),
searchClient: http.NewClient[SearchResult](clientArgs),
ioReader: os.Stdin,
machine: util.NewMachine(),
logger: args.Logger,
keychain: args.Keychain,
loginClient: http.NewClient[LoginResult](clientArgs),
searchClient: http.NewClient[SearchResult](clientArgs),
purchaseClient: http.NewClient[PurchaseResult](clientArgs),
ioReader: os.Stdin,
machine: util.NewMachine(),
logger: args.Logger,
}
}
29 changes: 19 additions & 10 deletions pkg/appstore/appstore_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ import (
)

func (a *appstore) Info() error {
data, err := a.keychain.Get("account")
if err != nil {
return errors.Wrap(err, "account was not found")
}

var account Account
err = json.Unmarshal(data, &account)
acc, err := a.account()
if err != nil {
return errors.Wrap(err, "failed to unmarshall account data")
return errors.Wrap(err, ErrorReadAccount.Error())
}

a.logger.Info().
Str("name", account.Name).
Str("email", account.Email).
Str("name", acc.Name).
Str("email", acc.Email).
Bool("succes", true).
Send()

return nil
}

func (a *appstore) account() (Account, error) {
data, err := a.keychain.Get("account")
if err != nil {
return Account{}, errors.Wrap(err, ErrorKeychainGet.Error())
}

var acc Account
err = json.Unmarshal(data, &acc)
if err != nil {
return Account{}, errors.Wrap(err, ErrorUnmarshal.Error())
}

return acc, err
}
4 changes: 2 additions & 2 deletions pkg/appstore/appstore_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var _ = Describe("AppStore (Info)", func() {
It("returns wrapped error", func() {
err := appstore.Info()
Expect(err).To(MatchError(ContainSubstring(testErr.Error())))
Expect(err).To(MatchError(ContainSubstring("account was not found")))
Expect(err).To(MatchError(ContainSubstring(ErrorReadAccount.Error())))
})
})

Expand All @@ -56,7 +56,7 @@ var _ = Describe("AppStore (Info)", func() {

It("fails to unmarshall JSON data", func() {
err := appstore.Info()
Expect(err).To(MatchError(ContainSubstring("failed to unmarshall account data")))
Expect(err).To(MatchError(ContainSubstring(ErrorUnmarshal.Error())))
})
})

Expand Down
31 changes: 11 additions & 20 deletions pkg/appstore/appstore_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,11 @@ type LoginResult struct {
func (a *appstore) Login(email, password, authCode string) error {
macAddr, err := a.machine.MacAddress()
if err != nil {
return errors.Wrap(err, "failed to read MAC address")
return errors.Wrap(err, ErrorReadMAC.Error())
}

guid := strings.ReplaceAll(strings.ToUpper(macAddr), ":", "")
a.logger.Debug().
Str("mac", macAddr).
Str("guid", guid).
Send()

a.logger.Debug().Str("mac", macAddr).Str("guid", guid).Send()
return a.login(email, password, authCode, guid, 0)
}

Expand All @@ -54,33 +50,28 @@ func (a *appstore) login(email, password, authCode, guid string, attempt int) er
request := a.loginRequest(email, password, authCode, guid)
res, err := a.loginClient.Send(request)
if err != nil {
return errors.Wrap(err, "request failed")
return errors.Wrap(err, ErrorRequest.Error())
}

if attempt == 0 && res.Data.FailureType == FailureTypeInvalidCredentials {
return a.login(email, password, authCode, guid, 1)
}

if res.Data.FailureType != "" && res.Data.CustomerMessage != "" {
a.logger.Debug().
Interface("response", res).
Send()
a.logger.Debug().Interface("response", res).Send()
return errors.New(res.Data.CustomerMessage)
}

if res.Data.FailureType != "" {
a.logger.Debug().
Interface("response", res).
Send()
return errors.New("unknown error occurred")
a.logger.Debug().Interface("response", res).Send()
return ErrorGeneric
}

if res.Data.FailureType == "" && authCode == "" && res.Data.CustomerMessage == CustomerMessageBadLogin {
a.logger.Warn().
Msg("enter 2FA code:")
a.logger.Warn().Msg("enter 2FA code:")
authCode, err = a.promptForAuthCode()
if err != nil {
return errors.Wrap(err, "failed to prompt for 2FA code")
return errors.Wrap(err, ErrorReadData.Error())
}

return a.login(email, password, authCode, guid, 0)
Expand All @@ -96,12 +87,12 @@ func (a *appstore) login(email, password, authCode, guid string, attempt int) er
DirectoryServicesID: res.Data.DirectoryServicesID,
})
if err != nil {
return errors.Wrap(err, "failed to marshall account data")
return errors.Wrap(err, ErrorMarshal.Error())
}

err = a.keychain.Set("account", data)
if err != nil {
return errors.Wrap(err, "failed to save account data in keychain")
return errors.Wrap(err, ErrorKeychainSet.Error())
}

a.logger.Info().
Expand Down Expand Up @@ -145,7 +136,7 @@ func (a *appstore) promptForAuthCode() (string, error) {
reader := bufio.NewReader(a.ioReader)
authCode, err := reader.ReadString('\n')
if err != nil {
return "", errors.Wrap(err, "failed to read string from stdin")
return "", errors.Wrap(err, ErrorReadData.Error())
}

return strings.Trim(authCode, "\n"), nil
Expand Down
8 changes: 4 additions & 4 deletions pkg/appstore/appstore_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var _ = Describe("AppStore (Login)", func() {
It("returns error", func() {
err := as.Login("", "", "")
Expect(err).To(MatchError(ContainSubstring(testErr.Error())))
Expect(err).To(MatchError(ContainSubstring("failed to read MAC address")))
Expect(err).To(MatchError(ContainSubstring(ErrorReadMAC.Error())))
})
})

Expand Down Expand Up @@ -121,7 +121,7 @@ var _ = Describe("AppStore (Login)", func() {

It("returns error", func() {
err := as.Login("", "", "")
Expect(err).To(MatchError(ContainSubstring("unknown error occurred")))
Expect(err).To(MatchError(ContainSubstring(ErrorGeneric.Error())))
})
})

Expand Down Expand Up @@ -180,7 +180,7 @@ var _ = Describe("AppStore (Login)", func() {

It("prompts user for 2FA code", func() {
err := as.Login("", "", "")
Expect(err).To(MatchError(ContainSubstring("failed to read string from stdin")))
Expect(err).To(MatchError(ContainSubstring(ErrorReadData.Error())))
})
})
})
Expand Down Expand Up @@ -237,7 +237,7 @@ var _ = Describe("AppStore (Login)", func() {
It("returns error", func() {
err := as.Login("", "", "")
Expect(err).To(MatchError(ContainSubstring(testErr.Error())))
Expect(err).To(MatchError(ContainSubstring("failed to save account data in keychain")))
Expect(err).To(MatchError(ContainSubstring(ErrorKeychainSet.Error())))
})
})

Expand Down
21 changes: 9 additions & 12 deletions pkg/appstore/appstore_lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,27 @@ import (
)

func (a *appstore) lookup(bundleID, countryCode, deviceFamily string) (App, error) {
if StoreFront[countryCode] == "" {
return App{}, errors.New("invalid country code")
if StoreFronts[countryCode] == "" {
return App{}, ErrorInvalidCountryCode
}

request, err := a.lookupRequest(bundleID, countryCode, deviceFamily)
if err != nil {
return App{}, errors.Wrap(err, "failed to get lookup request")
return App{}, errors.Wrap(err, ErrorCreateRequest.Error())
}

res, err := a.searchClient.Send(request)
if err != nil {
return App{}, errors.Wrap(err, "lookup request failed")
return App{}, errors.Wrap(err, ErrorRequest.Error())
}

if res.StatusCode != 200 {
a.logger.Debug().
Interface("data", res.Data).
Int("status", res.StatusCode).
Send()
return App{}, errors.Errorf("lookup request failed with status %d", res.StatusCode)
a.logger.Debug().Interface("data", res.Data).Int("status", res.StatusCode).Send()
return App{}, ErrorRequest
}

if len(res.Data.Results) == 0 {
return App{}, errors.New("app not found")
return App{}, ErrorAppNotFound
}

return res.Data.Results[0], nil
Expand All @@ -40,7 +37,7 @@ func (a *appstore) lookup(bundleID, countryCode, deviceFamily string) (App, erro
func (a *appstore) lookupRequest(bundleID, countryCode, deviceFamily string) (http.Request, error) {
lookupURL, err := a.lookupURL(bundleID, countryCode, deviceFamily)
if err != nil {
return http.Request{}, errors.Wrap(err, "failed to get lookup URL")
return http.Request{}, errors.Wrap(err, ErrorURL.Error())
}

return http.Request{
Expand All @@ -59,7 +56,7 @@ func (a *appstore) lookupURL(bundleID, countryCode, deviceFamily string) (string
case DeviceFamilyPad:
entity = "iPadSoftware"
default:
return "", errors.Errorf("device family is not supported: %s", deviceFamily)
return "", ErrorInvalidDeviceFamily
}

params := url.Values{}
Expand Down
10 changes: 5 additions & 5 deletions pkg/appstore/appstore_lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ var _ = Describe("AppStore (Lookup)", func() {
When("country code is invalid", func() {
It("returns error", func() {
_, err := as.lookup("", "XYZ", "")
Expect(err).To(MatchError(ContainSubstring("invalid country code")))
Expect(err).To(MatchError(ContainSubstring(ErrorInvalidCountryCode.Error())))
})
})

When("device family is invalid", func() {
It("returns error", func() {
_, err := as.lookup("", "US", "XYZ")
Expect(err).To(MatchError(ContainSubstring("device family is not supported: XYZ")))
Expect(err).To(MatchError(ContainSubstring(ErrorInvalidDeviceFamily.Error())))
})
})

Expand All @@ -59,7 +59,7 @@ var _ = Describe("AppStore (Lookup)", func() {
It("returns error", func() {
_, err := as.lookup("", "US", DeviceFamilyPhone)
Expect(err).To(MatchError(ContainSubstring(testErr.Error())))
Expect(err).To(MatchError(ContainSubstring("lookup request failed")))
Expect(err).To(MatchError(ContainSubstring(ErrorRequest.Error())))
})
})

Expand All @@ -78,7 +78,7 @@ var _ = Describe("AppStore (Lookup)", func() {

It("returns error", func() {
_, err := as.lookup("", "US", DeviceFamilyPad)
Expect(err).To(MatchError(ContainSubstring("lookup request failed with status 400")))
Expect(err).To(MatchError(ContainSubstring(ErrorRequest.Error())))
})
})

Expand All @@ -98,7 +98,7 @@ var _ = Describe("AppStore (Lookup)", func() {

It("returns error", func() {
_, err := as.lookup("", "US", DeviceFamilyPad)
Expect(err).To(MatchError(ContainSubstring("app not found")))
Expect(err).To(MatchError(ContainSubstring(ErrorAppNotFound.Error())))
})
})

Expand Down
Loading

0 comments on commit 6543934

Please sign in to comment.