Skip to content
This repository has been archived by the owner on Jan 2, 2024. It is now read-only.

Commit

Permalink
cmd/tier: add -cancel flag to subscribe command (#209)
Browse files Browse the repository at this point in the history
This adds support for canceling a subscription for an org via the
subscribe sub-command.
  • Loading branch information
bmizerany committed Jan 9, 2023
1 parent 59a04f8 commit 836ab6a
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cmd/tier/cline/cline.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (d *Data) RunFail(args ...string) {
func (d *Data) Run(args ...string) {
d.t.Helper()
if err := d.doRun(args...); err != nil {
d.t.Fatal(err)
d.t.Fatalf("unexpected failure: %v", err)
}
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/tier/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ Flags:
set the org's trial period to the provided number of days. If
negative, the tial period will last indefinitely, and no other
phase will come after it.
--cancel
cancel the org's subscription. It is an error to provide a plan
or featurePlan with this flag.
Global Flags:
Expand Down
13 changes: 13 additions & 0 deletions cmd/tier/tier.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ func runTier(cmd string, args []string) (err error) {
fs := flag.NewFlagSet(cmd, flag.ExitOnError)
email := fs.String("email", "", "sets the customer email address")
trial := fs.Int("trial", 0, "sets the trial period in days")
cancel := fs.Bool("cancel", false, "cancels the subscription")
if err := fs.Parse(args); err != nil {
return err
}
Expand All @@ -268,6 +269,17 @@ func runTier(cmd string, args []string) (err error) {
Email: *email,
},
}

// the cancel must be used without arguments
if *cancel && fs.NArg() > 1 {
fmt.Fprintln(stderr, "tier: the -cancel flag must be used without arguments")
return errUsage
}

if *cancel {
p.Phases = []tier.Phase{{}}
}

var refs []string
if fs.NArg() > 1 {
refs = fs.Args()[1:]
Expand All @@ -290,6 +302,7 @@ func runTier(cmd string, args []string) (err error) {
p.Phases = []tier.Phase{{Features: refs}}
}
}

vlogf("subscribing %s to %v", org, refs)
return tc().Schedule(ctx, org, p)
case "phases":
Expand Down
57 changes: 55 additions & 2 deletions cmd/tier/tier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"testing"
"time"

"golang.org/x/exp/slices"
"kr.dev/diff"
"tier.run/cmd/tier/cline"
"tier.run/profile"
Expand Down Expand Up @@ -489,6 +490,37 @@ func TestSubscribe(t *testing.T) {

tt.Run("subscribe", "org:test")
tt.GrepBothNot(".+", "unexpected output")

tt = testtier(t, okHandler(t))
tt.RunFail("subscribe", "--cancel", "org:test", "plan:free@2")
tt.GrepStderr("Usage:", ".*without arguments.*Usage:")

var got atomicSlice[string]
tt = testtier(t, func(w http.ResponseWriter, r *http.Request) {
t.Logf("got request: %s %s", r.Method, r.URL.Path)
switch {
case wants(r, "GET", "/v1/subscriptions"):
io.WriteString(w, `{
"data": [
{
"id": "sub_123",
"metadata": {
"tier.subscription": "default"
}
}
]
}`)
case wants(r, "DELETE", "/v1/subscriptions/sub_123"):
got.Append(r.URL.Path)
default:
io.WriteString(w, `{}`)
}
})

tt.Run("subscribe", "--cancel", "org:test")

want := []string{"/v1/subscriptions/sub_123"}
diff.Test(t, t.Errorf, got.Load(), want)
}

const responsePricesValidPlan = `
Expand Down Expand Up @@ -566,6 +598,27 @@ func chdir(t *testing.T, dir string) {
})
}

func wants(r *http.Request, method, path string) bool {
return r.Method == method && r.URL.Path == path
func wants(r *http.Request, method, pattern string) bool {
pattern = "^" + pattern + "$"
rx := regexp.MustCompile(pattern)
return r.Method == method && rx.MatchString(r.URL.Path)
}

type atomicSlice[T any] struct {
mu sync.Mutex
v []T
}

func (s *atomicSlice[T]) Append(v T) {
s.mu.Lock()
s.v = append(s.v, v)
s.mu.Unlock()
}

// Load returns a shallow clone of the slice.
func (s *atomicSlice[T]) Load() []T {
s.mu.Lock()
v := slices.Clone(s.v)
s.mu.Unlock()
return v
}
2 changes: 1 addition & 1 deletion control/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func isReleased(err error) bool {
}

func (c *Client) scheduleCancel(ctx context.Context, org string) (err error) {
defer errorfmt.Handlef("tier: ScheduleCancel: %q: %w", org, &err)
defer errorfmt.Handlef("tier: scheduleCancel: %q: %w", org, &err)
s, err := c.lookupSubscription(ctx, org, subscriptionNameTODO)
if err != nil {
return err
Expand Down

0 comments on commit 836ab6a

Please sign in to comment.