Skip to content

Commit

Permalink
App 6822: Adding CLI command to update the billing service (#4612)
Browse files Browse the repository at this point in the history
  • Loading branch information
RoxyFarhad authored Dec 9, 2024
1 parent 4e84156 commit ba259a2
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/app_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type BillingAddress struct {
AddressLine2 *string
City string
State string
Zipcode string
}

// Location holds the information of a specific location.
Expand Down Expand Up @@ -1856,6 +1857,7 @@ func billingAddressToProto(addr *BillingAddress) *pb.BillingAddress {
AddressLine_2: addr.AddressLine2,
City: addr.City,
State: addr.State,
Zipcode: addr.Zipcode,
}
}

Expand Down
18 changes: 18 additions & 0 deletions app/app_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,24 @@ func TestAppClient(t *testing.T) {
test.That(t, resp, test.ShouldEqual, "test-email")
})

t.Run("UpdateBillingServiceConfig", func(t *testing.T) {
grpcClient.UpdateBillingServiceFunc = func(ctx context.Context,
in *pb.UpdateBillingServiceRequest, opts ...grpc.CallOption,
) (*pb.UpdateBillingServiceResponse, error) {
test.That(t, in.OrgId, test.ShouldEqual, organizationID)
return &pb.UpdateBillingServiceResponse{}, nil
}

err := client.UpdateBillingService(context.Background(), organizationID, &BillingAddress{
AddressLine1: "address_line_1",
AddressLine2: nil,
City: "city",
State: "state",
Zipcode: "zip",
})
test.That(t, err, test.ShouldBeNil)
})

t.Run("GetOrganizationsWithAccessToLocation", func(t *testing.T) {
expectedOrganizationIdentities := []*OrganizationIdentity{&organizationIdentity}
grpcClient.GetOrganizationsWithAccessToLocationFunc = func(
Expand Down
18 changes: 18 additions & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const (
cpFlagPreserve = "preserve"

organizationFlagSupportEmail = "support-email"
organizationBillingAddress = "address"
)

var commonFilterFlags = []cli.Flag{
Expand Down Expand Up @@ -383,6 +384,23 @@ var app = &cli.App{
},
Action: GetBillingConfigAction,
},
{
Name: "update",
Usage: "update the billing service update for an organization",
Flags: []cli.Flag{
&cli.StringFlag{
Name: generalFlagOrgID,
Required: true,
Usage: "the org to update the billing service for",
},
&cli.StringFlag{
Name: organizationBillingAddress,
Required: true,
Usage: "the stringified address that follows the pattern: line1, line2 (optional), city, state, zipcode",
},
},
Action: UpdateBillingServiceAction,
},
},
},
{
Expand Down
49 changes: 49 additions & 0 deletions cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,55 @@ func (c *viamClient) organizationsSupportEmailGetAction(cCtx *cli.Context, orgID
return nil
}

// UpdateBillingServiceAction corresponds to `organizations billing-service update`.
func UpdateBillingServiceAction(cCtx *cli.Context) error {
c, err := newViamClient(cCtx)
if err != nil {
return err
}
orgID := cCtx.String(generalFlagOrgID)
if orgID == "" {
return errors.New("cannot update billing service without an organization ID")
}

address := cCtx.String(organizationBillingAddress)
if address == "" {
return errors.New("cannot update billing service to an empty address")
}

return c.updateBillingServiceAction(cCtx, orgID, address)
}

func (c *viamClient) updateBillingServiceAction(cCtx *cli.Context, orgID, addressAsString string) error {
if err := c.ensureLoggedIn(); err != nil {
return err
}
address, err := parseBillingAddress(addressAsString)
if err != nil {
return err
}

_, err = c.client.UpdateBillingService(cCtx.Context, &apppb.UpdateBillingServiceRequest{
OrgId: orgID,
BillingAddress: address,
})
if err != nil {
return err
}

printf(cCtx.App.Writer, "Successfully updated billing service for organization %q", orgID)
printf(cCtx.App.Writer, " --- Billing Address --- ")
printf(cCtx.App.Writer, "Address Line 1: %s", address.GetAddressLine_1())
if address.GetAddressLine_2() != "" {
printf(cCtx.App.Writer, "Address Line 2: %s", address.GetAddressLine_2())
}
printf(cCtx.App.Writer, "City: %s", address.GetCity())
printf(cCtx.App.Writer, "State: %s", address.GetState())
printf(cCtx.App.Writer, "Postal Code: %s", address.GetZipcode())
printf(cCtx.App.Writer, "Country: USA")
return nil
}

// GetBillingConfigAction corresponds to `organizations billing get`.
func GetBillingConfigAction(cCtx *cli.Context) error {
c, err := newViamClient(cCtx)
Expand Down
25 changes: 25 additions & 0 deletions cli/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,31 @@ func TestGetBillingConfigAction(t *testing.T) {
test.That(t, out.messages[11], test.ShouldContainSubstring, "USA")
}

func TestUpdateBillingServiceAction(t *testing.T) {
updateConfigFunc := func(ctx context.Context, in *apppb.UpdateBillingServiceRequest, opts ...grpc.CallOption) (
*apppb.UpdateBillingServiceResponse, error,
) {
return &apppb.UpdateBillingServiceResponse{}, nil
}
asc := &inject.AppServiceClient{
UpdateBillingServiceFunc: updateConfigFunc,
}

cCtx, ac, out, errOut := setup(asc, nil, nil, nil, nil, "token")
address := "123 Main St, Suite 100, San Francisco, CA, 94105"
test.That(t, ac.updateBillingServiceAction(cCtx, "test-org", address), test.ShouldBeNil)
test.That(t, len(errOut.messages), test.ShouldEqual, 0)
test.That(t, len(out.messages), test.ShouldEqual, 8)
test.That(t, out.messages[0], test.ShouldContainSubstring, "Successfully updated billing service for organization")
test.That(t, out.messages[1], test.ShouldContainSubstring, " --- Billing Address --- ")
test.That(t, out.messages[2], test.ShouldContainSubstring, "123 Main St")
test.That(t, out.messages[3], test.ShouldContainSubstring, "Suite 100")
test.That(t, out.messages[4], test.ShouldContainSubstring, "San Francisco")
test.That(t, out.messages[5], test.ShouldContainSubstring, "CA")
test.That(t, out.messages[6], test.ShouldContainSubstring, "94105")
test.That(t, out.messages[7], test.ShouldContainSubstring, "USA")
}

func TestTabularDataByFilterAction(t *testing.T) {
pbStruct, err := protoutils.StructToStructPb(map[string]interface{}{"bool": true, "string": "true", "float": float64(1)})
test.That(t, err, test.ShouldBeNil)
Expand Down
33 changes: 33 additions & 0 deletions cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/pkg/errors"
apppb "go.viam.com/api/app/v1"
)

// samePath returns true if abs(path1) and abs(path2) are the same.
Expand Down Expand Up @@ -59,3 +63,32 @@ func ParseFileType(raw string) string {
}
return fmt.Sprintf("%s/%s", osLookup[rawOs], archLookup[rawArch])
}

func parseBillingAddress(address string) (*apppb.BillingAddress, error) {
if address == "" {
return nil, errors.New("address is empty")
}

splitAddress := strings.Split(address, ",")
if len(splitAddress) != 4 && len(splitAddress) != 5 {
return nil, errors.Errorf("address: %s does not follow the format: line1, line2 (optional), city, state, zipcode", address)
}

if len(splitAddress) == 4 {
return &apppb.BillingAddress{
AddressLine_1: strings.Trim(splitAddress[0], " "),
City: strings.Trim(splitAddress[1], " "),
State: strings.Trim(splitAddress[2], " "),
Zipcode: strings.Trim(splitAddress[3], " "),
}, nil
}

line2 := strings.Trim(splitAddress[1], " ")
return &apppb.BillingAddress{
AddressLine_1: strings.Trim(splitAddress[0], " "),
AddressLine_2: &line2,
City: strings.Trim(splitAddress[2], " "),
State: strings.Trim(splitAddress[3], " "),
Zipcode: strings.Trim(splitAddress[4], " "),
}, nil
}
51 changes: 51 additions & 0 deletions cli/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package cli
import (
"testing"

"github.com/pkg/errors"
apppb "go.viam.com/api/app/v1"
"go.viam.com/test"
)

Expand Down Expand Up @@ -36,3 +38,52 @@ func TestParseFileType(t *testing.T) {
test.That(t, ParseFileType(pair[1]), test.ShouldResemble, pair[0])
}
}

func TestParseBillingAddress(t *testing.T) {
addressLine2 := "Apt 1"

testCases := []struct {
input string
expectedAddress *apppb.BillingAddress
expectedErr error
}{
{
input: "123 Main St, Apt 1, San Francisco, CA, 94105",
expectedAddress: &apppb.BillingAddress{
AddressLine_1: "123 Main St",
AddressLine_2: &addressLine2,
City: "San Francisco",
State: "CA",
Zipcode: "94105",
},
},
{
input: "123 Main St, San Francisco, CA, 94105",
expectedAddress: &apppb.BillingAddress{
AddressLine_1: "123 Main St",
City: "San Francisco",
State: "CA",
Zipcode: "94105",
},
},
{
input: "an-invalid address, city-1",
expectedAddress: nil,
expectedErr: errors.New("address: an-invalid address, city-1 does not follow the format: line1, line2 (optional), city, state, zipcode"),
},
{
input: "",
expectedAddress: nil,
expectedErr: errors.New("address is empty"),
},
}

for _, tc := range testCases {
address, err := parseBillingAddress(tc.input)
if tc.expectedErr != nil {
test.That(t, err.Error(), test.ShouldNotBeNil)
test.That(t, err.Error(), test.ShouldContainSubstring, tc.expectedErr.Error())
}
test.That(t, address, test.ShouldResembleProto, tc.expectedAddress)
}
}

0 comments on commit ba259a2

Please sign in to comment.