Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ docker compose run --rm sim-cli gateways create \
--firmware 1.0.0 \
--freq 1000

# Bulk create 5 gateways
# Bulk create gateways with one shared config and multiple factory IDs
docker compose run --rm sim-cli gateways bulk \
--count 5 \
--factory-id FAC-001 \
--factory-id FAC-002 \
--factory-id FAC-003 \
--factory-key KEY-001 \
--model GW-X \
--firmware 1.0.0 \
Expand Down
19 changes: 11 additions & 8 deletions cmd/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ const (
// process, which would otherwise cause test pollution when the full suite runs.
func resetAllFlags(c *cobra.Command) {
c.Flags().VisitAll(func(f *pflag.Flag) {
_ = f.Value.Set(f.DefValue)
if slice, ok := f.Value.(interface{ Replace([]string) error }); ok {
_ = slice.Replace(nil)
} else {
_ = f.Value.Set(f.DefValue)
}
f.Changed = false
})
for _, child := range c.Commands() {
Expand Down Expand Up @@ -141,15 +145,14 @@ func TestGatewaysCreateMissingRequiredFlags(t *testing.T) {
}
}

func TestGatewaysBulkMissingCount(t *testing.T) {
func TestGatewaysBulkMissingFactoryID(t *testing.T) {
if err := runCmd("gateways", "bulk",
testFlagFactoryID, "f",
testFlagFactoryKey, "k",
testFlagModel, "GW-X",
testFlagFirmware, "1.0.0",
testFlagFreq, "1000",
); err == nil {
t.Error("expected error when --count is missing")
t.Error("expected error when --factory-id is missing")
}
}

Expand Down Expand Up @@ -438,8 +441,8 @@ func TestGatewaysBulkServerError(t *testing.T) {
http.Error(w, "server error", http.StatusInternalServerError)
})
err := runCmd("gateways", "bulk",
"--count", "2",
testFlagFactoryID, "f",
testFlagFactoryID, "f-1",
testFlagFactoryID, "f-2",
testFlagFactoryKey, "k",
testFlagModel, "GW-X",
testFlagFirmware, "1.0.0",
Expand All @@ -458,8 +461,8 @@ func TestGatewaysBulkPartialErrors(t *testing.T) {
})
})
err := runCmd("gateways", "bulk",
"--count", "2",
testFlagFactoryID, "f",
testFlagFactoryID, "f-1",
testFlagFactoryID, "f-2",
testFlagFactoryKey, "k",
testFlagModel, "GW-X",
testFlagFirmware, "1.0.0",
Expand Down
28 changes: 22 additions & 6 deletions cmd/gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"strconv"
"strings"

"github.com/NoTIPswe/notip-simulator-cli/internal/client"
"github.com/pterm/pterm"
Expand Down Expand Up @@ -123,15 +124,20 @@ var gatewaysBulkCmd = &cobra.Command{
Short: "Create multiple gateways at once (POST /sim/gateways/bulk)",
RunE: func(cmd *cobra.Command, args []string) error {
req := client.BulkCreateGatewaysRequest{}
req.Count, _ = cmd.Flags().GetInt("count")
req.FactoryID, _ = cmd.Flags().GetString(flagFactoryID)
factoryIDs, _ := cmd.Flags().GetStringSlice(flagFactoryID)
factoryIDs = normalizeFactoryIDs(factoryIDs)
if len(factoryIDs) == 0 {
return fmt.Errorf("at least one --factory-id is required")
}
req.FactoryIDs = factoryIDs

req.FactoryKey, _ = cmd.Flags().GetString(flagFactoryKey)
req.Model, _ = cmd.Flags().GetString("model")
req.FirmwareVersion, _ = cmd.Flags().GetString("firmware")
req.SendFrequencyMs, _ = cmd.Flags().GetInt("freq")

spinner := startSpinner(
fmt.Sprintf("Creating %d gateway(s)...", req.Count),
fmt.Sprintf("Creating %d gateway(s)...", len(req.FactoryIDs)),
)
c := client.New(simulatorURL).WithContext(cmd.Context())
result, err := c.BulkCreateGateways(req)
Expand Down Expand Up @@ -252,6 +258,17 @@ func gatewayUUID(gw client.Gateway) string {
return gw.ID
}

func normalizeFactoryIDs(factoryIDs []string) []string {
out := make([]string, 0, len(factoryIDs))
for _, factoryID := range factoryIDs {
trimmed := strings.TrimSpace(factoryID)
if trimmed != "" {
out = append(out, trimmed)
}
}
return out
}

// ── init ──────────────────────────────────────────────────────────────────────

func init() {
Expand All @@ -277,13 +294,12 @@ func init() {
}

// bulk flags
gatewaysBulkCmd.Flags().Int("count", 1, "Number of gateways to create (required)")
gatewaysBulkCmd.Flags().String(flagFactoryID, "", "Factory ID (required)")
gatewaysBulkCmd.Flags().StringSlice(flagFactoryID, nil, "Factory ID (required, repeat flag to create one gateway per ID)")
gatewaysBulkCmd.Flags().String(flagFactoryKey, "", "Factory key (required)")
gatewaysBulkCmd.Flags().String("model", "", "Gateway model (required)")
gatewaysBulkCmd.Flags().String("firmware", "", "Firmware version (required)")
gatewaysBulkCmd.Flags().Int("freq", 1000, "Send frequency in milliseconds (required)")
for _, f := range []string{"count", flagFactoryID, flagFactoryKey, "model", "firmware", "freq"} {
for _, f := range []string{flagFactoryID, flagFactoryKey, "model", "firmware", "freq"} {
mustMarkRequired(gatewaysBulkCmd, f)
}
}
20 changes: 16 additions & 4 deletions cmd/request_mapping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,20 @@ func TestGatewaysBulkFlagToJSONMapping(t *testing.T) {
t.Errorf(fmtUnexpectedRequest, r.Method, r.URL.Path)
}
body := readBody(t, r)
checkKey(t, body, "count", float64(5))
checkKey(t, body, "factoryId", "fac-bulk")
checkAbsent(t, body, "count")
checkAbsent(t, body, "factoryId")

rawFactoryIDs, ok := body["factoryIds"].([]any)
if !ok {
t.Fatalf("factoryIds must be an array, got %#v", body["factoryIds"])
}
if len(rawFactoryIDs) != 2 {
t.Fatalf("want 2 factoryIds, got %d", len(rawFactoryIDs))
}
if rawFactoryIDs[0] != "fac-bulk-1" || rawFactoryIDs[1] != "fac-bulk-2" {
t.Fatalf("unexpected factoryIds payload: %#v", rawFactoryIDs)
}

checkKey(t, body, "factoryKey", "key-bulk")
checkKey(t, body, "model", "GW-MINI")
checkKey(t, body, "firmwareVersion", "1.2.3")
Expand All @@ -133,8 +145,8 @@ func TestGatewaysBulkFlagToJSONMapping(t *testing.T) {
})

err := runCmd("gateways", "bulk",
"--count", "5",
testFlagFactoryID, "fac-bulk",
testFlagFactoryID, "fac-bulk-1",
testFlagFactoryID, "fac-bulk-2",
testFlagFactoryKey, "key-bulk",
testFlagModel, "GW-MINI",
testFlagFirmware, "1.2.3",
Expand Down
6 changes: 5 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ func Execute() error {
// Cobra flag state is sticky within the same process, which affects shell mode.
func resetAllCommandFlags(c *cobra.Command) {
c.Flags().VisitAll(func(f *pflag.Flag) {
_ = f.Value.Set(f.DefValue)
if slice, ok := f.Value.(interface{ Replace([]string) error }); ok {
_ = slice.Replace(nil)
} else {
_ = f.Value.Set(f.DefValue)
}
f.Changed = false
})
for _, child := range c.Commands() {
Expand Down
11 changes: 5 additions & 6 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,11 @@ type CreateGatewayRequest struct {

// BulkCreateGatewaysRequest is the payload for POST /sim/gateways/bulk.
type BulkCreateGatewaysRequest struct {
Count int `json:"count"`
FactoryID string `json:"factoryId"`
FactoryKey string `json:"factoryKey"`
Model string `json:"model,omitempty"`
FirmwareVersion string `json:"firmwareVersion,omitempty"`
SendFrequencyMs int `json:"sendFrequencyMs,omitempty"`
FactoryIDs []string `json:"factoryIds"`
FactoryKey string `json:"factoryKey"`
Model string `json:"model,omitempty"`
FirmwareVersion string `json:"firmwareVersion,omitempty"`
SendFrequencyMs int `json:"sendFrequencyMs,omitempty"`
}

// BulkCreateResponse is the response for POST /sim/gateways/bulk.
Expand Down
11 changes: 5 additions & 6 deletions internal/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ func TestBulkCreateGatewaysAllSuccess(t *testing.T) {

var req client.BulkCreateGatewaysRequest
decodeBody(t, r, &req)
if req.Count != 2 {
t.Errorf("count = %d, want 2", req.Count)
if len(req.FactoryIDs) != 2 {
t.Errorf("factoryIds length = %d, want 2", len(req.FactoryIDs))
}
writeJSON(w, http.StatusCreated, want)
})

got, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{Count: 2, FactoryID: "f", FactoryKey: "k"})
got, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{FactoryIDs: []string{"f-1", "f-2"}, FactoryKey: "k"})
if err != nil {
t.Fatalf(errFmtUnexpected, err)
}
Expand All @@ -135,7 +135,7 @@ func TestBulkCreateGatewaysPartialErrors207(t *testing.T) {
writeJSON(w, http.StatusMultiStatus, want)
})

got, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{Count: 2, FactoryID: "f", FactoryKey: "k"})
got, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{FactoryIDs: []string{"f-1", "f-2"}, FactoryKey: "k"})
if err != nil {
t.Fatalf("unexpected error on 207: %v", err)
}
Expand Down Expand Up @@ -497,8 +497,7 @@ func TestBulkCreateGatewaysInvalidJSONResponse(t *testing.T) {
})

_, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{
Count: 1,
FactoryID: "f-1",
FactoryIDs: []string{"f-1"},
FactoryKey: "k-1",
})
if err == nil {
Expand Down
21 changes: 16 additions & 5 deletions internal/client/request_construction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,20 @@ func TestBulkCreateGatewaysRequestConstruction(t *testing.T) {
assertContentType(t, r)

body := readBodyAsMap(t, r)
assertKey(t, body, "count", float64(3))
assertKey(t, body, "factoryId", "fac-bulk")
assertKeyAbsent(t, body, "count")
assertKeyAbsent(t, body, "factoryId")

rawFactoryIDs, ok := body["factoryIds"].([]any)
if !ok {
t.Fatalf("factoryIds should be an array, got %#v", body["factoryIds"])
}
if len(rawFactoryIDs) != 3 {
t.Fatalf("factoryIds length = %d, want 3", len(rawFactoryIDs))
}
if rawFactoryIDs[0] != "fac-bulk-1" || rawFactoryIDs[1] != "fac-bulk-2" || rawFactoryIDs[2] != "fac-bulk-3" {
t.Fatalf("unexpected factoryIds payload: %#v", rawFactoryIDs)
}

assertKey(t, body, "factoryKey", "key-bulk")
assertKey(t, body, "model", "GW-BULK")
assertKey(t, body, "firmwareVersion", "1.0.0")
Expand All @@ -156,8 +168,7 @@ func TestBulkCreateGatewaysRequestConstruction(t *testing.T) {
})

_, err := c.BulkCreateGateways(client.BulkCreateGatewaysRequest{
Count: 3,
FactoryID: "fac-bulk",
FactoryIDs: []string{"fac-bulk-1", "fac-bulk-2", "fac-bulk-3"},
FactoryKey: "key-bulk",
Model: "GW-BULK",
FirmwareVersion: "1.0.0",
Expand All @@ -176,7 +187,7 @@ func TestBulkCreateGatewaysOptionalFieldsOmittedWhenZero(t *testing.T) {
assertKeyAbsent(t, body, "sendFrequencyMs")
writeJSON(w, http.StatusCreated, client.BulkCreateResponse{})
})
_, _ = c.BulkCreateGateways(client.BulkCreateGatewaysRequest{Count: 1, FactoryID: "f", FactoryKey: "k"})
_, _ = c.BulkCreateGateways(client.BulkCreateGatewaysRequest{FactoryIDs: []string{"f"}, FactoryKey: "k"})
}

// ── GET /sim/gateways — no body ───────────────────────────────────────────────
Expand Down