Repository: google/go-github
Pull request #732 (2017-09-28 20:54:34)

#720 Initial commit for listing Plans and Plan Accounts
* Includes basic testing for Listing Plans and the Accounts linked to a Plan (including stubs)

**NOTE** This is currently awaiting scrutiny as it's my first contribution to the project. I would appreciate any feedback (good or bad) and the 👍  if it's ok to carry on with the rest of the implementation for #720 

googlebot: Thanks for your pull request.  It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed, please reply here (e.g. `I signed it!`) and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If your company signed a CLA, they designated a Point of Contact who decides which employees are authorized to participate. You may need to contact the Point of Contact for your company and ask to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to go/cla#troubleshoot.
- In order to pass this check, please resolve this problem and have the pull request author add another comment and the bot will run again.

<!-- need_sender_cla -->
lackerman: I signed it!
googlebot: CLAs look good, thanks!

<!-- ok -->
lackerman: @shurcooL Regarding
> ...I think we should factor out that stubbed bool argument from each method. Perhaps, instead, it should be a new option that we add to github.Client. What do you think about that?...

I'm so happy you said that. The flag felt like suck a hack. Not sure I have enough context or experience to comment on the preferred solution, but I'm happy to implement it ;).

lackerman: Hi @shurcooL, could you perhaps tell me why the build is failing? I see it's during the `go generate` step, but I'm not sure how the code I've written has impacted.
e-beach: @lackerman You need to run the command `go generate` and commit and push, this will generate some accessors for structs.

shurcooL: Nice work on resolving the build failure.

> I'm so happy you said that. The flag felt like suck a hack. Not sure I have enough context or experience to comment on the preferred solution, but I'm happy to implement it ;).

Great! The next step here, as far as I can tell, is to factor out that `stubbed bool` argument from all the new methods.

Looking at it a bit closer, I think we can move it into `MarketplaceService` struct rather than into the [`github.Client`](https://godoc.org/github.com/google/go-github/github#Client) struct. Perhaps it can look something like this:

```Go
// MarketplaceService handles communication with the marketplace related
// methods of the GitHub API.
//
// GitHub API docs: https://developer.github.com/v3/apps/marketplace/
type MarketplaceService struct {
	client *Client

	// Stubbed controls whether endpoints that return stubbed data are used
	// instead of production endpoints. Stubbed data is fake data that's useful
	// for testing your GitHub Apps. Stubbed data is hard-coded and will not
	// change based on actual subscriptions.
	//
	// GitHub API docs: https://developer.github.com/v3/apps/marketplace/
	Stubbed bool
}
```

Then, users can set it to true if they wish:

```Go
client := github.NewClient(nil)
client.MarketplaceService.Stubbed = true
// ...
```
lackerman: Hi @shurcooL what about using a Options wrapper?

```go
// MarketplaceOptions specifies the optional parameters to the Marketplace service methods
type MarketplaceOptions struct {
	// Stubbed controls whether endpoints that return stubbed data are used
	// instead of production endpoints. Stubbed data is fake data that's useful
	// for testing your GitHub Apps. Stubbed data is hard-coded and will not
	// change based on actual subscriptions.
	//
	// GitHub API docs: https://developer.github.com/v3/apps/marketplace/
	Stubbed bool

	ListOptions
}
```

And then only passing `ListOptions` to `addOptions`

```
uri := marketplaceURI(opt, "plans")
u, err := addOptions(uri, opt.ListOptions)
```

Or is this pattern specifically used for optional query parameters in the Url?
shurcooL: `MarketplaceOptions` would work too. So it's just a design decision that's up to us to make.

I think it largely depends on how often one wants to set Stubbed to true. From my current understanding how it's meant to be used, it makes more sense to me to set it once for the entire service while doing testing, rather than for each individual endpoint via the options.

So, unless I'm not understanding the use case properly, I still prefer my `client.MarketplaceService.Stubbed = true` suggestion above.

@gmlewis @elliott-beach Do you have thoughts/preferences on how to deal with the stubbed parameter?
lackerman: Hi @shurcooL, this is probably due to my own ignorance of the language, but when I try your suggested implementation, I get:
```
# github.com/google/go-github/github
./github.go:231:39: cannot convert &c.common (type *service) to 
type *MarketplaceService
```
since the Service is of type `service` by default but now it's being declared as type `struct`.  Is this because I'm missing something that will allow it to _implement_ the service interface?
shurcooL: @lackerman You wouldn't be able to reuse `&c.common` struct anymore, because `*MarketplaceService` will have an extra field. You'll have to create a new `*MarketplaceService` struct, like so:

```diff
-c.Marketplace = (*MarketplaceService)(&c.common)
+c.Marketplace = &MarketplaceService{client: c}
```
lackerman: Thanks for the help @shurcooL. Please could I ask you to explain the difference between the following two calling conventions:
`(*MarketplaceService)(&c.common)` and `&MarketplaceService{client: c}`
The second one makes obvious sense but I'm not seeing how (*XXX)(...) creates a valid instance.
shurcooL: @lackerman It works for other services because they're defined as `service`, e.g.:

```Go
type ActivityService service
```

So `*service` is convertible to `*ActivityService`. E.g., see [here](https://play.golang.org/p/mWxWK_RPhG).

You can find more information about this in #389 and #390 that introduced it. Note, this is a pretty advanced trick, it's not commonly done, and the benefits it offers are nice but not critical. It's a nice to have.
lackerman: Ah, a colleague pointed out that it's just a type cast. Due to the syntax `(*type)(...)`, I thought it was some kind or function call/syntactic sugar for new or &. 
shurcooL: Yes, it is. Although if you want to be precise about terms, it's a "type conversion". See https://golang.org/ref/spec#Conversions:

> Conversions are expressions of the form T(x) where T is a type and x is an expression that can be converted to type T.
>
> If the type starts with the operator * or <-, or if the type starts with the keyword func and has no result list, it must be parenthesized when necessary to avoid ambiguity

Hence `(*T)(x)`.
lackerman: Hi @shurcooL, I've completed the unit tests and performed a basic test to my account on one of the stubbed endpoints and it appears to work as expected.
shurcooL: Good to hear!

I'm not seeing any new commits. Did you mean to push anything that implements the changes we discussed above?
lackerman: @shurcooL, sad panda...yeah, I forgot to push. It's pushed now.
lackerman: @shurcooL fixed the issue you pointed out.
gmlewis: Sorry for the delay - yes, I like the `MarketplaceService.Stubbed` solution that @shurcooL proposed in the [comment above](https://github.com/google/go-github/pull/732#issuecomment-333726743).
I'll now review the PR.
lackerman: Thanks @gmlewis for the extra pair of eyes, really appreciate the suggestions.
lackerman: @shurcooL, updated

lackerman: @shurcooL not sure why the build failed after adding a comment line
shurcooL: We have some issues with gen-accessors right now, I've filed #778 for it. But fixing that is out of scope of this PR, so just re-generate the package, and it should be fine.
lackerman: @shurcooL ready to be merged

Patch:
diff --git a/github/apps_marketplace.go b/github/apps_marketplace.go
new file mode 100644
index 00000000..d3e689a1
--- /dev/null
+++ b/github/apps_marketplace.go
@@ -0,0 +1,180 @@
+// Copyright 2017 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+)
+
+// MarketplaceService handles communication with the marketplace related
+// methods of the GitHub API.
+//
+// GitHub API docs: https://developer.github.com/v3/apps/marketplace/
+type MarketplaceService struct {
+	client *Client
+	// Stubbed controls whether endpoints that return stubbed data are used
+	// instead of production endpoints. Stubbed data is fake data that's useful
+	// for testing your GitHub Apps. Stubbed data is hard-coded and will not
+	// change based on actual subscriptions.
+	//
+	// GitHub API docs: https://developer.github.com/v3/apps/marketplace/
+	Stubbed bool
+}
+
+// MarketplacePlan represents a GitHub Apps Marketplace Listing Plan.
+type MarketplacePlan struct {
+	URL                 *string   `json:"url,omitempty"`
+	AccountsURL         *string   `json:"accounts_url,omitempty"`
+	ID                  *int      `json:"id,omitempty"`
+	Name                *string   `json:"name,omitempty"`
+	Description         *string   `json:"description,omitempty"`
+	MonthlyPriceInCents *int      `json:"monthly_price_in_cents,omitempty"`
+	YearlyPriceInCents  *int      `json:"yearly_price_in_cents,omitempty"`
+	PriceModel          *string   `json:"price_model,omitempty"`
+	UnitName            *string   `json:"unit_name,omitempty"`
+	Bullets             *[]string `json:"bullets,omitempty"`
+}
+
+// MarketplacePurchase represents a GitHub Apps Marketplace Purchase.
+type MarketplacePurchase struct {
+	BillingCycle    *string                 `json:"billing_cycle,omitempty"`
+	NextBillingDate *string                 `json:"next_billing_date,omitempty"`
+	UnitCount       *int                    `json:"unit_count,omitempty"`
+	Plan            *MarketplacePlan        `json:"plan,omitempty"`
+	Account         *MarketplacePlanAccount `json:"account,omitempty"`
+}
+
+// MarketplacePlanAccount represents a GitHub Account (user or organization) on a specific plan.
+type MarketplacePlanAccount struct {
+	URL                      *string              `json:"url,omitempty"`
+	Type                     *string              `json:"type,omitempty"`
+	ID                       *int                 `json:"id,omitempty"`
+	Login                    *string              `json:"login,omitempty"`
+	Email                    *string              `json:"email,omitempty"`
+	OrganizationBillingEmail *string              `json:"organization_billing_email,omitempty"`
+	MarketplacePurchase      *MarketplacePurchase `json:"marketplace_purchase,omitempty"`
+}
+
+// ListPlans lists all plans for your Marketplace listing.
+//
+// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-all-plans-for-your-marketplace-listing
+func (s *MarketplaceService) ListPlans(ctx context.Context, opt *ListOptions) ([]*MarketplacePlan, *Response, error) {
+	uri := s.marketplaceURI("plans")
+	u, err := addOptions(uri, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeMarketplacePreview)
+
+	var plans []*MarketplacePlan
+	resp, err := s.client.Do(ctx, req, &plans)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return plans, resp, nil
+}
+
+// ListPlanAccountsForPlan lists all GitHub accounts (user or organization) on a specific plan.
+//
+// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-all-github-accounts-user-or-organization-on-a-specific-plan
+func (s *MarketplaceService) ListPlanAccountsForPlan(ctx context.Context, planID int, opt *ListOptions) ([]*MarketplacePlanAccount, *Response, error) {
+	uri := s.marketplaceURI(fmt.Sprintf("plans/%v/accounts", planID))
+	u, err := addOptions(uri, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeMarketplacePreview)
+
+	var accounts []*MarketplacePlanAccount
+	resp, err := s.client.Do(ctx, req, &accounts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return accounts, resp, nil
+}
+
+// ListPlanAccountsForAccount lists all GitHub accounts (user or organization) associated with an account.
+//
+// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#check-if-a-github-account-is-associated-with-any-marketplace-listing
+func (s *MarketplaceService) ListPlanAccountsForAccount(ctx context.Context, accountID int, opt *ListOptions) ([]*MarketplacePlanAccount, *Response, error) {
+	uri := s.marketplaceURI(fmt.Sprintf("accounts/%v", accountID))
+	u, err := addOptions(uri, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeMarketplacePreview)
+
+	var accounts []*MarketplacePlanAccount
+	resp, err := s.client.Do(ctx, req, &accounts)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return accounts, resp, nil
+}
+
+// ListMarketplacePurchasesForUser lists all GitHub marketplace purchases made by a user.
+//
+// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#get-a-users-marketplace-purchases
+func (s *MarketplaceService) ListMarketplacePurchasesForUser(ctx context.Context, opt *ListOptions) ([]*MarketplacePurchase, *Response, error) {
+	uri := "user/marketplace_purchases"
+	if s.Stubbed {
+		uri = "user/marketplace_purchases/stubbed"
+	}
+
+	u, err := addOptions(uri, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeMarketplacePreview)
+
+	var purchases []*MarketplacePurchase
+	resp, err := s.client.Do(ctx, req, &purchases)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return purchases, resp, nil
+}
+
+func (s *MarketplaceService) marketplaceURI(endpoint string) string {
+	url := "marketplace_listing"
+	if s.Stubbed {
+		url = "marketplace_listing/stubbed"
+	}
+	return url + "/" + endpoint
+}
diff --git a/github/github-accessors.go b/github/github-accessors.go
index c5b7b6b9..821cd83a 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -500,6 +500,14 @@ func (c *Client) GetLicenses() *LicensesService {
 	return c.Licenses
 }
 
+// GetMarketplace returns the Marketplace field.
+func (c *Client) GetMarketplace() *MarketplaceService {
+	if c == nil {
+		return nil
+	}
+	return c.Marketplace
+}
+
 // GetMigrations returns the Migrations field.
 func (c *Client) GetMigrations() *MigrationService {
 	if c == nil {
@@ -3836,6 +3844,190 @@ func (m *markdownRequest) GetText() string {
 	return *m.Text
 }
 
+// GetAccountsURL returns the AccountsURL field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetAccountsURL() string {
+	if m == nil || m.AccountsURL == nil {
+		return ""
+	}
+	return *m.AccountsURL
+}
+
+// GetBullets returns the Bullets field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetBullets() []string {
+	if m == nil || m.Bullets == nil {
+		return nil
+	}
+	return *m.Bullets
+}
+
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetDescription() string {
+	if m == nil || m.Description == nil {
+		return ""
+	}
+	return *m.Description
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetID() int {
+	if m == nil || m.ID == nil {
+		return 0
+	}
+	return *m.ID
+}
+
+// GetMonthlyPriceInCents returns the MonthlyPriceInCents field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetMonthlyPriceInCents() int {
+	if m == nil || m.MonthlyPriceInCents == nil {
+		return 0
+	}
+	return *m.MonthlyPriceInCents
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetName() string {
+	if m == nil || m.Name == nil {
+		return ""
+	}
+	return *m.Name
+}
+
+// GetPriceModel returns the PriceModel field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetPriceModel() string {
+	if m == nil || m.PriceModel == nil {
+		return ""
+	}
+	return *m.PriceModel
+}
+
+// GetUnitName returns the UnitName field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetUnitName() string {
+	if m == nil || m.UnitName == nil {
+		return ""
+	}
+	return *m.UnitName
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetURL() string {
+	if m == nil || m.URL == nil {
+		return ""
+	}
+	return *m.URL
+}
+
+// GetYearlyPriceInCents returns the YearlyPriceInCents field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlan) GetYearlyPriceInCents() int {
+	if m == nil || m.YearlyPriceInCents == nil {
+		return 0
+	}
+	return *m.YearlyPriceInCents
+}
+
+// GetEmail returns the Email field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetEmail() string {
+	if m == nil || m.Email == nil {
+		return ""
+	}
+	return *m.Email
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetID() int {
+	if m == nil || m.ID == nil {
+		return 0
+	}
+	return *m.ID
+}
+
+// GetLogin returns the Login field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetLogin() string {
+	if m == nil || m.Login == nil {
+		return ""
+	}
+	return *m.Login
+}
+
+// GetMarketplacePurchase returns the MarketplacePurchase field.
+func (m *MarketplacePlanAccount) GetMarketplacePurchase() *MarketplacePurchase {
+	if m == nil {
+		return nil
+	}
+	return m.MarketplacePurchase
+}
+
+// GetOrganizationBillingEmail returns the OrganizationBillingEmail field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetOrganizationBillingEmail() string {
+	if m == nil || m.OrganizationBillingEmail == nil {
+		return ""
+	}
+	return *m.OrganizationBillingEmail
+}
+
+// GetType returns the Type field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetType() string {
+	if m == nil || m.Type == nil {
+		return ""
+	}
+	return *m.Type
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (m *MarketplacePlanAccount) GetURL() string {
+	if m == nil || m.URL == nil {
+		return ""
+	}
+	return *m.URL
+}
+
+// GetAccount returns the Account field.
+func (m *MarketplacePurchase) GetAccount() *MarketplacePlanAccount {
+	if m == nil {
+		return nil
+	}
+	return m.Account
+}
+
+// GetBillingCycle returns the BillingCycle field if it's non-nil, zero value otherwise.
+func (m *MarketplacePurchase) GetBillingCycle() string {
+	if m == nil || m.BillingCycle == nil {
+		return ""
+	}
+	return *m.BillingCycle
+}
+
+// GetNextBillingDate returns the NextBillingDate field if it's non-nil, zero value otherwise.
+func (m *MarketplacePurchase) GetNextBillingDate() string {
+	if m == nil || m.NextBillingDate == nil {
+		return ""
+	}
+	return *m.NextBillingDate
+}
+
+// GetPlan returns the Plan field.
+func (m *MarketplacePurchase) GetPlan() *MarketplacePlan {
+	if m == nil {
+		return nil
+	}
+	return m.Plan
+}
+
+// GetUnitCount returns the UnitCount field if it's non-nil, zero value otherwise.
+func (m *MarketplacePurchase) GetUnitCount() int {
+	if m == nil || m.UnitCount == nil {
+		return 0
+	}
+	return *m.UnitCount
+}
+
+// Getclient returns the client field.
+func (m *MarketplaceService) Getclient() *Client {
+	if m == nil {
+		return nil
+	}
+	return m.client
+}
+
 // GetText returns the Text field if it's non-nil, zero value otherwise.
 func (m *Match) GetText() string {
 	if m == nil || m.Text == nil {
diff --git a/github/github.go b/github/github.go
index db06cfb4..3a0727eb 100644
--- a/github/github.go
+++ b/github/github.go
@@ -103,6 +103,9 @@ const (
 	// https://developer.github.com/changes/2017-07-26-team-review-request-thor-preview/
 	mediaTypeTeamReviewPreview = "application/vnd.github.thor-preview+json"
 
+	// https://developer.github.com/v3/apps/marketplace/
+	mediaTypeMarketplacePreview = "application/vnd.github.valkyrie-preview+json"
+
 	// https://developer.github.com/changes/2017-08-30-preview-nested-teams/
 	mediaTypeNestedTeamsPreview = "application/vnd.github.hellcat-preview+json"
 )
@@ -137,15 +140,16 @@ type Client struct {
 	Git            *GitService
 	Gitignores     *GitignoresService
 	Issues         *IssuesService
+	Licenses       *LicensesService
+	Marketplace    *MarketplaceService
+	Migrations     *MigrationService
 	Organizations  *OrganizationsService
 	Projects       *ProjectsService
 	PullRequests   *PullRequestsService
+	Reactions      *ReactionsService
 	Repositories   *RepositoriesService
 	Search         *SearchService
 	Users          *UsersService
-	Licenses       *LicensesService
-	Migrations     *MigrationService
-	Reactions      *ReactionsService
 }
 
 type service struct {
@@ -227,6 +231,7 @@ func NewClient(httpClient *http.Client) *Client {
 	c.Gitignores = (*GitignoresService)(&c.common)
 	c.Issues = (*IssuesService)(&c.common)
 	c.Licenses = (*LicensesService)(&c.common)
+	c.Marketplace = &MarketplaceService{client: c}
 	c.Migrations = (*MigrationService)(&c.common)
 	c.Organizations = (*OrganizationsService)(&c.common)
 	c.Projects = (*ProjectsService)(&c.common)


Test patch:
diff --git a/github/apps_marketplace_test.go b/github/apps_marketplace_test.go
new file mode 100644
index 00000000..151a7217
--- /dev/null
+++ b/github/apps_marketplace_test.go
@@ -0,0 +1,202 @@
+// Copyright 2017 The go-github AUTHORS. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package github
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestMarketplaceService_ListPlans(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/plans", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		testFormValues(t, r, values{
+			"page":     "1",
+			"per_page": "2",
+		})
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = false
+	plans, _, err := client.Marketplace.ListPlans(context.Background(), opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlans returned error: %v", err)
+	}
+
+	want := []*MarketplacePlan{{ID: Int(1)}}
+	if !reflect.DeepEqual(plans, want) {
+		t.Errorf("Marketplace.ListPlans returned %+v, want %+v", plans, want)
+	}
+}
+
+func TestMarketplaceService_Stubbed_ListPlans(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/stubbed/plans", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = true
+	plans, _, err := client.Marketplace.ListPlans(context.Background(), opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlans (Stubbed) returned error: %v", err)
+	}
+
+	want := []*MarketplacePlan{{ID: Int(1)}}
+	if !reflect.DeepEqual(plans, want) {
+		t.Errorf("Marketplace.ListPlans (Stubbed) returned %+v, want %+v", plans, want)
+	}
+}
+
+func TestMarketplaceService_ListPlanAccountsForPlan(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = false
+	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(context.Background(), 1, opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlanAccountsForPlan returned error: %v", err)
+	}
+
+	want := []*MarketplacePlanAccount{{ID: Int(1)}}
+	if !reflect.DeepEqual(accounts, want) {
+		t.Errorf("Marketplace.ListPlanAccountsForPlan returned %+v, want %+v", accounts, want)
+	}
+}
+
+func TestMarketplaceService_Stubbed_ListPlanAccountsForPlan(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/stubbed/plans/1/accounts", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = true
+	accounts, _, err := client.Marketplace.ListPlanAccountsForPlan(context.Background(), 1, opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned error: %v", err)
+	}
+
+	want := []*MarketplacePlanAccount{{ID: Int(1)}}
+	if !reflect.DeepEqual(accounts, want) {
+		t.Errorf("Marketplace.ListPlanAccountsForPlan (Stubbed) returned %+v, want %+v", accounts, want)
+	}
+}
+
+func TestMarketplaceService_ListPlanAccountsForAccount(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/accounts/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = false
+	accounts, _, err := client.Marketplace.ListPlanAccountsForAccount(context.Background(), 1, opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlanAccountsForAccount returned error: %v", err)
+	}
+
+	want := []*MarketplacePlanAccount{{ID: Int(1)}}
+	if !reflect.DeepEqual(accounts, want) {
+		t.Errorf("Marketplace.ListPlanAccountsForAccount returned %+v, want %+v", accounts, want)
+	}
+}
+
+func TestMarketplaceService_Stubbed_ListPlanAccountsForAccount(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/marketplace_listing/stubbed/accounts/1", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = true
+	accounts, _, err := client.Marketplace.ListPlanAccountsForAccount(context.Background(), 1, opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListPlanAccountsForAccount (Stubbed) returned error: %v", err)
+	}
+
+	want := []*MarketplacePlanAccount{{ID: Int(1)}}
+	if !reflect.DeepEqual(accounts, want) {
+		t.Errorf("Marketplace.ListPlanAccountsForAccount (Stubbed) returned %+v, want %+v", accounts, want)
+	}
+}
+
+func TestMarketplaceService_ListMarketplacePurchasesForUser(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/user/marketplace_purchases", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = false
+	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(context.Background(), opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
+	}
+
+	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
+	if !reflect.DeepEqual(purchases, want) {
+		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
+	}
+}
+
+func TestMarketplaceService_Stubbed_ListMarketplacePurchasesForUser(t *testing.T) {
+	setup()
+	defer teardown()
+
+	mux.HandleFunc("/user/marketplace_purchases/stubbed", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeMarketplacePreview)
+		fmt.Fprint(w, `[{"billing_cycle":"monthly"}]`)
+	})
+
+	opt := &ListOptions{Page: 1, PerPage: 2}
+	client.Marketplace.Stubbed = true
+	purchases, _, err := client.Marketplace.ListMarketplacePurchasesForUser(context.Background(), opt)
+	if err != nil {
+		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned error: %v", err)
+	}
+
+	want := []*MarketplacePurchase{{BillingCycle: String("monthly")}}
+	if !reflect.DeepEqual(purchases, want) {
+		t.Errorf("Marketplace.ListMarketplacePurchasesForUser returned %+v, want %+v", purchases, want)
+	}
+}