ability to mock stores stubbed

This commit is contained in:
2026-01-30 00:55:17 -07:00
parent b1593e8202
commit e74854bf6e
12 changed files with 566 additions and 699 deletions
+3 -2
View File
@@ -3,6 +3,7 @@ package consts
import "errors"
var (
ErrNotFound = errors.New("not found")
ErrConflict = errors.New("conflict")
ErrNotFound = errors.New("not found")
ErrConflict = errors.New("conflict")
ErrBadRequest = errors.New("bad request")
)
+258
View File
@@ -10,6 +10,7 @@ import (
"ruben/inventory2/internal/consts"
"ruben/inventory2/internal/logging"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -59,6 +60,11 @@ type (
AccountShopIDs
ListingID string
}
MockShop struct {
AccountShopIDs
Name string
}
)
func NewStore(logger *logging.Logger, db *pgxpool.Pool) *Store {
@@ -309,6 +315,258 @@ func (db *Store) GetListingsForShop(ctx context.Context, acctID int64, platform
return vs, nil
}
func (db *Store) CreateMockShop(ctx context.Context, acctID int64, platform Platform, name string) (shopID uuid.UUID, err error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return uuid.Nil, err
}
tx, err := db.db.Begin(ctx)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to being transaction: %w", err)
}
defer tx.Rollback(ctx)
// TODO: are we really going to just copy the id between accounts and mock.accounts?
// upsert mock account
rows, err := tx.Query(
ctx,
`
WITH existing_account(account_id, user_id) AS (
SELECT
account_id,
user_id
FROM
accounts
WHERE
account_id = @account_id
), existing_mock_account AS (
SELECT
user_id
FROM
accounts
WHERE
account_id = @account_id
), inserted_mock_account AS (
INSERT INTO
mock.accounts (
account_id,
user_id
)
SELECT
account_id,
user_id
FROM
existing_account
ON CONFLICT
DO NOTHING
RETURNING
user_id
)
SELECT
COALESCE(ia.user_id, ea.user_id)
FROM
inserted_mock_account ia
FULL OUTER JOIN
existing_mock_account ea
USING
(user_id)
`,
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("test query failed: %w", err)
}
userID, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return uuid.Nil, fmt.Errorf("%w: account not found", consts.ErrNotFound)
}
return uuid.Nil, fmt.Errorf("failed to scan rows: %w", err)
}
// create the mock shop
shopID = uuid.New()
_, err = tx.Exec(
ctx,
fmt.Sprintf(
`
INSERT INTO
%s (
user_id,
account_id,
shop_id,
name
)
VALUES (
@user_id,
@account_id,
@shop_id,
@name
)
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"user_id": userID,
"shop_id": shopID,
"name": name,
},
)
if err != nil {
return uuid.Nil, fmt.Errorf("failed to perform query to create mock shop: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return uuid.Nil, fmt.Errorf("failed to commit transaction: %w", err)
}
return shopID, nil
}
func (db *Store) ListMockShopsForPlatform(ctx context.Context, acctID int64, platform Platform) ([]MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
shop_id,
name
FROM
%s
WHERE
account_id = @account_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
vs, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Shop_id uuid.UUID
Name string
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
shops := make([]MockShop, len(vs))
for i, v := range vs {
shops[i] = MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: v.Shop_id.String(),
},
Name: v.Name,
}
}
return shops, nil
}
func (db *Store) GetMockShop(ctx context.Context, acctID int64, platform Platform, shopID string) (*MockShop, error) {
shopTableName, err := getMockShopTableName(platform)
if err != nil {
return nil, err
}
rows, err := db.db.Query(
ctx,
fmt.Sprintf(
`
SELECT
name
FROM
%s
WHERE
account_id = @account_id
AND shop_id = @shop_id
`,
shopTableName,
),
pgx.NamedArgs{
"account_id": acctID,
"shop_id": shopID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
name, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[string])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, consts.ErrNotFound
}
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
return &MockShop{
AccountShopIDs: AccountShopIDs{
AccountIDs: AccountIDs{
AccountID: acctID,
},
Platform: platform,
ShopID: shopID,
},
Name: name,
}, nil
}
func getMockShopTableName(platform Platform) (string, error) {
switch platform {
case Amazon:
return "mock.shop_amazon", nil
case BigCartel:
return "mock.shop_big_cartel", nil
case Ebay:
return "mock.shop_ebay", nil
case Ecwid:
return "mock.shop_ecwid", nil
case Etsy:
return "mock.shop_etsy", nil
case Shopify:
return "mock.shop_shopify", nil
case SquareOnline:
return "mock.shop_square_online", nil
case Squarespace:
return "mock.shop_squarespace", nil
case Tiktok:
return "mock.shop_tiktok", nil
case WalmartMarketplace:
return "mock.shop_walmart_marketplace", nil
case Wix:
return "mock.shop_wix", nil
case WooCommerce:
return "mock.shop_woo_commerce", nil
case Zoho:
return "mock.shop_zoho", nil
default:
return "", fmt.Errorf("%w: unrecognized platform", consts.ErrBadRequest)
}
}
// additional context
func (db *Store) GetAccountPointerByUserID(ctx context.Context, userID string) (*Account, error) {
@@ -5,6 +5,7 @@ package accounts
import (
"context"
"github.com/google/uuid"
)
type StoreWithContext struct {
@@ -47,6 +48,18 @@ func (v_ctx *StoreWithContext) GetListingsForShop(acctID int64, platform Platfor
return v_ctx.Store.GetListingsForShop(v_ctx.ctx, acctID, platform, shopID)
}
func (v_ctx *StoreWithContext) CreateMockShop(acctID int64, platform Platform, name string) (uuid.UUID, error) {
return v_ctx.Store.CreateMockShop(v_ctx.ctx, acctID, platform, name)
}
func (v_ctx *StoreWithContext) ListMockShopsForPlatform(acctID int64, platform Platform) ([]MockShop, error) {
return v_ctx.Store.ListMockShopsForPlatform(v_ctx.ctx, acctID, platform)
}
func (v_ctx *StoreWithContext) GetMockShop(acctID int64, platform Platform, shopID string) (*MockShop, error) {
return v_ctx.Store.GetMockShop(v_ctx.ctx, acctID, platform, shopID)
}
func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Account, error) {
return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID)
}
+51 -1
View File
@@ -36,7 +36,10 @@ func Routes(
}
r.POST("", response.Handler(as.createAccount))
r.PUT("/:acctID/platforms/:platform/order-index", pub.Publish("/:acctID/platforms"), response.Handler(as.setOrderOfPlatformOnAccountPage))
platformGroup := r.Group("/:acctID/platforms/:platform", pub.Publish("/:acctID/platforms"))
platformGroup.PUT("/order-index", response.Handler(as.setOrderOfPlatformOnAccountPage))
platformGroup.POST("/shops/mocks", response.Handler(as.createMockShop))
syncGroups := r.Group("/:acctID/inventory/sync-groups")
syncGroups.POST("", pub.Publish("/:acctID/inventory/sync-groups"), response.Handler(as.saveNewSyncGroup))
@@ -251,6 +254,53 @@ func (s *accountSubrouter) setOrderOfPlatformOnAccountPage(c *gin.Context) (resp
return response.StatusNoContent(), nil
}
// POST /:acctID/platforms/:platform/shops/mocks
func (s *accountSubrouter) createMockShop(c *gin.Context) (response.Response, error) {
acctID := auth.GetIdentity(c).Account.AccountID
// validate parameters
var platform accounts.Platform
if v := c.Param("platform"); v == "" {
return nil, response.BadRequest().
Msg("no platform provided")
} else if p, err := accounts.NewPlatform(v); err != nil {
return nil, response.BadRequest().
Wrap(err).
Msgf("unrecognized platform: %v", v)
} else {
platform = p
}
var name string
if v, ok := c.GetPostForm("name"); !ok || v == "" {
return nil, response.BadRequest().
Msg("no order-index provided")
} else {
name = v
}
s.log.Warnf("not implemented: mock store created: account_id = %d; platform = %s; name = %s", acctID, platform, name)
// create the mock account
id, err := s.accts.CreateMockShop(c, acctID, platform, name)
if err != nil {
return nil, fmt.Errorf("failed to save record: %w", err)
}
location := fmt.Sprintf("/ui/accounts/%d/platforms/%s/mock-shops/%s", acctID, lowerSnakeCase(platform), id)
var res response.Response
if c.GetHeader("HX-Request") == "true" {
res = response.StatusCreated()
} else {
res = response.SeeOther(location)
}
return res.HXLocation(location), nil
}
func lowerSnakeCase(s accounts.Platform) string {
return strings.ToLower(strings.Join(strings.Split(string(s), " "), "_"))
}
+15
View File
@@ -69,6 +69,21 @@ func Routes(
"lowerSnakeCase": func(s string) string {
return strings.ToLower(strings.Join(strings.Split(s, " "), "_"))
},
"splitSnakeCase": func(s string) string {
return strings.Join(strings.Split(s, "_"), " ")
},
"capitalize": func(s string) string {
ss := strings.Split(s, " ")
us := make([]string, len(ss))
for i, v := range ss {
if len(v) == 0 {
us[i] = v
} else {
us[i] = strings.ToUpper(v[:1]) + v[1:]
}
}
return strings.Join(us, " ")
},
// arithmetic
"addInt": func(a, b int) int {