Skip to content

Commit 06fbbaa

Browse files
bmsaadatclaude
andauthored
Add session name & tags to browsers and acquire (kernel-go-sdk v0.65.0) (#177)
Adds `name` and `tags` support to the browser commands — `browsers create/get/update/delete/list` and `browser-pools acquire` — modeled on the existing `profiles` CLI and the hypeman `--tag KEY=VALUE` convention: create/acquire set them, get/list/JSON echo them, `list` gains a `--tag` filter plus a Name column, and lookups now accept `<id-or-name>`. This requires bumping `kernel-go-sdk` v0.58.0 → v0.65.0, whose unrelated breaking changes are absorbed in a separate first commit — all list endpoints became paginated (so `browser-pools`/`proxies`/`credential-providers`/`extensions` list now read pages and gain `--limit/--offset` flags), proxy config unions were renamed, and the proxy API dropped `carrier` entirely. The PR is two clean commits — migration first, then feature — and adds unit tests across the new flags, the tag parser, and the shared acquire-params helper. Note one user-visible change: `kernel proxies create --carrier` is no longer accepted (mobile create also no longer takes `zip/asn`), because the upstream API removed mobile carrier selection. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > SDK bump changes list/pagination and proxy create semantics (dropped `--carrier`); behavior is covered by tests but users with mobile carrier scripts will break. > > **Overview** > Bumps **kernel-go-sdk** to v0.65.0 and adds **browser session `name` and `tags`** across the CLI: `browsers create` / pool acquire accept `--name` and repeatable `--tag KEY=VALUE`; `list` filters with `--tag`, shows a **Name** column, and broadens `--query`; **get/update/delete/view** take `<id-or-name>`. Pool leases share **`buildAcquireParams`** so `browser-pools acquire` and `browsers create --pool-*` forward name/tags the same way. > > The SDK upgrade also drives **paginated list** handling for browser-pools, proxies, extensions, and credential-providers (**`--limit` / `--offset`**), **renamed proxy config unions**, and **removal of mobile `--carrier`** (plus warnings when `--zip`/`--asn` are passed for mobile). README and unit tests cover the new flags, tag parsing, and acquire helper. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 188bbf9. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --- ## Manual testing Tested end-to-end against a **locally running Kernel API** (`KERNEL_BASE_URL=http://localhost:3001`) using the CLI built from this branch — creating, querying, and deleting real browsers, pools, and proxies and asserting both the CLI output and the request params forwarded to the API. **All scenarios below passed locally; every test resource was cleaned up afterward.** | Area | Scenario | Result | |---|---|---| | Build / auth | CLI builds, authenticates, and reaches the local API | ✅ pass | | Flags / help | `create` exposes `--name`/`--tag`; `list` exposes `--query`/`--tag`/`--limit`/`--offset`; `acquire` exposes `--name`/`--tag`; all four migrated list commands expose `--limit`/`--offset` | ✅ pass | | Relabel | `get`/`view`/`update`/`delete` show `<id-or-name>`; `update` no-arg error and `delete` usage corrected | ✅ pass | | Create | `--name` + repeatable `--tag` → name and sorted tags in table **and** JSON | ✅ pass | | Create | duplicate name rejected (`Conflict: browser session name already exists`) | ✅ pass | | Create | special-character tag key (`region.us=1`) round-trips | ✅ pass | | get / view | resolve by **ID and by name** (same session); name/tags in detail + JSON; `view -o json` → `{liveViewUrl}`; not-found handled | ✅ pass | | update | resolves by name and applies change; `-o json` echoes name/tags; no `--name`/`--tag` flags (creation-only) | ✅ pass | | delete | mixed ID + name in one call; idempotent on already-deleted | ✅ pass | | list | Name column; **`--tag` deepObject filter**; **multiple `--tag` ANDed**; `--query` matches name; JSON carries name/tags | ✅ pass | | pool acquire | `--name`/`--tag` applied per-lease and echoed; **cleared on release** | ✅ pass | | create-from-pool | `--pool-name --name --tag` **forwards name/tags to the acquired lease**; both-pool-flags rejected | ✅ pass | | proxies migration | datacenter/isp/residential/mobile create OK; mobile `--zip/--asn` warns and still creates; residential keeps zip/asn; **`--carrier` flag gone, no Carrier in get/list/check** | ✅ pass | | pagination | `--limit`/`--offset` on browsers / pools / proxies list | ✅ pass | | negative | `proxies create --carrier` → `Unknown flag`; `-o yaml` rejected; `app list` unaffected | ✅ pass | Environment notes (not CLI issues): the local stack throttles rapid successive creates (spaced requests succeed) and does not enforce the documented 50-tag max; custom-proxy create requires a reachable proxy (the server connection-tests it). The `create --pool-* --stealth` conflict still uses an interactive confirm that blocks under non-TTY — pre-existing behavior, worth a follow-up. The ">20 items, no silent truncation" case wasn't exercised (would need many live browsers); the pagination mechanism itself was validated via `--limit`/`--offset`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 0757525 commit 06fbbaa

21 files changed

Lines changed: 692 additions & 139 deletions

README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,25 +205,29 @@ Commands with JSON output support:
205205
### Browser Management
206206

207207
- `kernel browsers list` - List running browsers
208+
- `--query <q>` - Search by name, session ID, profile ID, proxy ID, or pool name
209+
- `--tag <KEY=VALUE>` - Filter by tag, repeatable; a session must match every pair
208210
- `--output json`, `-o json` - Output raw JSON array
209211
- `kernel browsers create` - Create a new browser session
210212
- `-s, --stealth` - Launch browser in stealth mode to avoid detection
211213
- `-H, --headless` - Launch browser without GUI access
212214
- `--kiosk` - Launch browser in kiosk mode
213215
- `--start-url <url>` - Initial page to open on launch
214-
- `--pool-id <id>` - Acquire a browser from the specified pool (mutually exclusive with --pool-name; ignores other session flags)
216+
- `--name <name>` - Optional unique name for the session (set at creation; used to find it later by name)
217+
- `--tag <KEY=VALUE>` - Set a tag on the session, repeatable; up to 50 pairs
218+
- `--pool-id <id>` - Acquire a browser from the specified pool (mutually exclusive with --pool-name; ignores other session flags). `--name`/`--tag` still apply to the acquired session.
215219
- `--pool-name <name>` - Acquire a browser from the pool name (mutually exclusive with --pool-id; ignores other session flags)
216220
- `--telemetry=all` - Enable telemetry for all categories
217221
- `--telemetry=off` - Disable telemetry
218222
- `--telemetry=<list>` - Per-category config, e.g. `--telemetry=network=on,page=off`
219223
- `--output json`, `-o json` - Output raw JSON object
220224
- _Note: When a pool is specified, omit other session configuration flags—pool settings determine profile, proxy, viewport, etc._
221-
- `kernel browsers delete <id>` - Delete a browser
222-
- `kernel browsers view <id>` - Get live view URL for a browser
225+
- `kernel browsers delete <id-or-name>` - Delete a browser by ID or name
226+
- `kernel browsers view <id-or-name>` - Get live view URL for a browser by ID or name
223227
- `--output json`, `-o json` - Output JSON with liveViewUrl
224-
- `kernel browsers get <id>` - Get detailed browser session info
228+
- `kernel browsers get <id-or-name>` - Get detailed browser session info by ID or name
225229
- `--output json`, `-o json` - Output raw JSON object
226-
- `kernel browsers update <id>` - Update a running browser session
230+
- `kernel browsers update <id-or-name>` - Update a running browser session by ID or name
227231
- `--telemetry=all` - Enable telemetry for all categories
228232
- `--telemetry=off` - Disable telemetry
229233
- `--telemetry=<list>` - Per-category config, e.g. `--telemetry=network=on,page=off`
@@ -264,6 +268,8 @@ Commands with JSON output support:
264268
- `--force` - Force delete even if browsers are leased
265269
- `kernel browser-pools acquire <id-or-name>` - Acquire a browser from the pool
266270
- `--timeout <seconds>` - Acquire timeout before returning 204
271+
- `--name <name>` - Optional name for the acquired session (applies to this lease; cleared on release)
272+
- `--tag <KEY=VALUE>` - Set a tag on the acquired session, repeatable; applies to this lease
267273
- `--output json`, `-o json` - Output raw JSON object
268274
- `kernel browser-pools release <id-or-name>` - Release a browser back to the pool
269275
- `--session-id <id>` - Browser session ID to release (required)
@@ -457,10 +463,9 @@ Per-category updates are partial — only categories you name are changed; other
457463
- `--country <code>` - ISO 3166 country code or "EU" (location-based types)
458464
- `--city <name>` - City name (no spaces, e.g. sanfrancisco) (residential, mobile; requires `--country`)
459465
- `--state <code>` - Two-letter state code (residential, mobile)
460-
- `--zip <zip>` - US ZIP code (residential, mobile)
461-
- `--asn <asn>` - Autonomous system number (e.g., AS15169) (residential, mobile)
466+
- `--zip <zip>` - US ZIP code (residential)
467+
- `--asn <asn>` - Autonomous system number (e.g., AS15169) (residential)
462468
- `--os <os>` - Operating system: windows, macos, android (residential)
463-
- `--carrier <carrier>` - Mobile carrier (mobile)
464469
- `--host <host>` - Proxy host (custom; required)
465470
- `--port <port>` - Proxy port (custom; required)
466471
- `--username <username>` - Username for proxy authentication (custom)
@@ -781,8 +786,8 @@ kernel proxies create --type custom --host proxy.example.com --port 8080 --usern
781786
# Create a residential proxy with location and OS
782787
kernel proxies create --type residential --country US --city sanfrancisco --state CA --zip 94107 --asn AS15169 --os windows --name "SF Residential"
783788

784-
# Create a mobile proxy with carrier
785-
kernel proxies create --type mobile --country US --carrier verizon --name "US Mobile"
789+
# Create a mobile proxy
790+
kernel proxies create --type mobile --country US --city sanfrancisco --name "US Mobile"
786791

787792
# Get proxy details
788793
kernel proxies get prx_123

cmd/browser_pools.go

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import (
88
"github.com/kernel/cli/pkg/util"
99
"github.com/kernel/kernel-go-sdk"
1010
"github.com/kernel/kernel-go-sdk/option"
11+
"github.com/kernel/kernel-go-sdk/packages/pagination"
1112
"github.com/pterm/pterm"
1213
"github.com/spf13/cobra"
1314
)
1415

1516
// BrowserPoolsService defines the subset of the Kernel SDK browser pools client that we use.
1617
type BrowserPoolsService interface {
17-
List(ctx context.Context, opts ...option.RequestOption) (res *[]kernel.BrowserPool, err error)
18+
List(ctx context.Context, query kernel.BrowserPoolListParams, opts ...option.RequestOption) (res *pagination.OffsetPagination[kernel.BrowserPool], err error)
1819
New(ctx context.Context, body kernel.BrowserPoolNewParams, opts ...option.RequestOption) (res *kernel.BrowserPool, err error)
1920
Get(ctx context.Context, id string, opts ...option.RequestOption) (res *kernel.BrowserPool, err error)
2021
Update(ctx context.Context, id string, body kernel.BrowserPoolUpdateParams, opts ...option.RequestOption) (res *kernel.BrowserPool, err error)
@@ -29,6 +30,8 @@ type BrowserPoolsCmd struct {
2930
}
3031

3132
type BrowserPoolsListInput struct {
33+
Limit int
34+
Offset int
3235
Output string
3336
}
3437

@@ -37,20 +40,33 @@ func (c BrowserPoolsCmd) List(ctx context.Context, in BrowserPoolsListInput) err
3740
return err
3841
}
3942

40-
pools, err := c.client.List(ctx)
43+
params := kernel.BrowserPoolListParams{}
44+
if in.Limit > 0 {
45+
params.Limit = kernel.Int(int64(in.Limit))
46+
}
47+
if in.Offset > 0 {
48+
params.Offset = kernel.Int(int64(in.Offset))
49+
}
50+
51+
page, err := c.client.List(ctx, params)
4152
if err != nil {
4253
return util.CleanedUpSdkError{Err: err}
4354
}
4455

56+
var pools []kernel.BrowserPool
57+
if page != nil {
58+
pools = page.Items
59+
}
60+
4561
if in.Output == "json" {
46-
if pools == nil || len(*pools) == 0 {
62+
if len(pools) == 0 {
4763
fmt.Println("[]")
4864
return nil
4965
}
50-
return util.PrintPrettyJSONSlice(*pools)
66+
return util.PrintPrettyJSONSlice(pools)
5167
}
5268

53-
if pools == nil || len(*pools) == 0 {
69+
if len(pools) == 0 {
5470
pterm.Info.Println("No browser pools found")
5571
return nil
5672
}
@@ -59,7 +75,7 @@ func (c BrowserPoolsCmd) List(ctx context.Context, in BrowserPoolsListInput) err
5975
{"ID", "Name", "Available", "Acquired", "Created At", "Size"},
6076
}
6177

62-
for _, p := range *pools {
78+
for _, p := range pools {
6379
tableData = append(tableData, []string{
6480
p.ID,
6581
util.OrDash(p.Name),
@@ -250,7 +266,7 @@ func (c BrowserPoolsCmd) Update(ctx context.Context, in BrowserPoolsUpdateInput)
250266
params.Name = kernel.String(in.Name)
251267
}
252268
if in.Size > 0 {
253-
params.Size = in.Size
269+
params.Size = kernel.Int(in.Size)
254270
}
255271
if in.FillRate > 0 {
256272
params.FillRatePerMinute = kernel.Int(in.FillRate)
@@ -338,18 +354,34 @@ func (c BrowserPoolsCmd) Delete(ctx context.Context, in BrowserPoolsDeleteInput)
338354
type BrowserPoolsAcquireInput struct {
339355
IDOrName string
340356
TimeoutSeconds int64
357+
Name string
358+
Tags map[string]string
341359
Output string
342360
}
343361

362+
// buildAcquireParams builds the SDK params for acquiring a browser from a pool.
363+
// Shared by `browser-pools acquire` and the `browsers create --pool-id/--pool-name`
364+
// path so the per-lease name/tags forwarding cannot silently diverge between them.
365+
func buildAcquireParams(name string, tags map[string]string, timeoutSeconds int64) kernel.BrowserPoolAcquireParams {
366+
params := kernel.BrowserPoolAcquireParams{}
367+
if timeoutSeconds > 0 {
368+
params.AcquireTimeoutSeconds = kernel.Int(timeoutSeconds)
369+
}
370+
if name != "" {
371+
params.Name = kernel.Opt(name)
372+
}
373+
if len(tags) > 0 {
374+
params.Tags = kernel.Tags(tags)
375+
}
376+
return params
377+
}
378+
344379
func (c BrowserPoolsCmd) Acquire(ctx context.Context, in BrowserPoolsAcquireInput) error {
345380
if err := validateJSONOutput(in.Output); err != nil {
346381
return err
347382
}
348383

349-
params := kernel.BrowserPoolAcquireParams{}
350-
if in.TimeoutSeconds > 0 {
351-
params.AcquireTimeoutSeconds = kernel.Int(in.TimeoutSeconds)
352-
}
384+
params := buildAcquireParams(in.Name, in.Tags, in.TimeoutSeconds)
353385
resp, err := c.client.Acquire(ctx, in.IDOrName, params)
354386
if err != nil {
355387
return util.CleanedUpSdkError{Err: err}
@@ -370,12 +402,20 @@ func (c BrowserPoolsCmd) Acquire(ctx context.Context, in BrowserPoolsAcquireInpu
370402
tableData := pterm.TableData{
371403
{"Property", "Value"},
372404
{"Session ID", resp.SessionID},
373-
{"CDP WebSocket URL", resp.CdpWsURL},
374-
{"Live View URL", resp.BrowserLiveViewURL},
375405
}
406+
if resp.Name != "" {
407+
tableData = append(tableData, []string{"Name", resp.Name})
408+
}
409+
tableData = append(tableData,
410+
[]string{"CDP WebSocket URL", resp.CdpWsURL},
411+
[]string{"Live View URL", resp.BrowserLiveViewURL},
412+
)
376413
if resp.StartURL != "" {
377414
tableData = append(tableData, []string{"Start URL", resp.StartURL})
378415
}
416+
if len(resp.Tags) > 0 {
417+
tableData = append(tableData, []string{"Tags", formatTags(resp.Tags)})
418+
}
379419
PrintTableNoPad(tableData, true)
380420
return nil
381421
}
@@ -482,6 +522,8 @@ var browserPoolsFlushCmd = &cobra.Command{
482522

483523
func init() {
484524
addJSONOutputFlag(browserPoolsListCmd)
525+
browserPoolsListCmd.Flags().Int("limit", 0, "Maximum number of pools to return")
526+
browserPoolsListCmd.Flags().Int("offset", 0, "Number of pools to skip (for pagination)")
485527

486528
addJSONOutputFlag(browserPoolsCreateCmd)
487529
browserPoolsCreateCmd.Flags().String("name", "", "Optional unique name for the pool")
@@ -523,6 +565,8 @@ func init() {
523565
browserPoolsDeleteCmd.Flags().Bool("force", false, "Force delete even if browsers are leased")
524566

525567
browserPoolsAcquireCmd.Flags().Int64("timeout", 0, "Acquire timeout in seconds")
568+
browserPoolsAcquireCmd.Flags().String("name", "", "Optional name for the acquired session (applies to this lease; cleared on release)")
569+
browserPoolsAcquireCmd.Flags().StringArray("tag", nil, "Set a tag KEY=VALUE on the acquired session (repeatable; applies to this lease)")
526570
addJSONOutputFlag(browserPoolsAcquireCmd)
527571

528572
browserPoolsReleaseCmd.Flags().String("session-id", "", "Browser session ID to release")
@@ -542,8 +586,10 @@ func init() {
542586
func runBrowserPoolsList(cmd *cobra.Command, args []string) error {
543587
client := getKernelClient(cmd)
544588
out, _ := cmd.Flags().GetString("output")
589+
limit, _ := cmd.Flags().GetInt("limit")
590+
offset, _ := cmd.Flags().GetInt("offset")
545591
c := BrowserPoolsCmd{client: &client.BrowserPools}
546-
return c.List(cmd.Context(), BrowserPoolsListInput{Output: out})
592+
return c.List(cmd.Context(), BrowserPoolsListInput{Limit: limit, Offset: offset, Output: out})
547593
}
548594

549595
func runBrowserPoolsCreate(cmd *cobra.Command, args []string) error {
@@ -656,9 +702,17 @@ func runBrowserPoolsDelete(cmd *cobra.Command, args []string) error {
656702
func runBrowserPoolsAcquire(cmd *cobra.Command, args []string) error {
657703
client := getKernelClient(cmd)
658704
timeout, _ := cmd.Flags().GetInt64("timeout")
705+
name, _ := cmd.Flags().GetString("name")
706+
tags := tagsFromFlag(cmd, "tag")
659707
output, _ := cmd.Flags().GetString("output")
660708
c := BrowserPoolsCmd{client: &client.BrowserPools}
661-
return c.Acquire(cmd.Context(), BrowserPoolsAcquireInput{IDOrName: args[0], TimeoutSeconds: timeout, Output: output})
709+
return c.Acquire(cmd.Context(), BrowserPoolsAcquireInput{
710+
IDOrName: args[0],
711+
TimeoutSeconds: timeout,
712+
Name: name,
713+
Tags: tags,
714+
Output: output,
715+
})
662716
}
663717

664718
func runBrowserPoolsRelease(cmd *cobra.Command, args []string) error {

cmd/browser_pools_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/kernel/kernel-go-sdk"
8+
"github.com/kernel/kernel-go-sdk/option"
9+
"github.com/kernel/kernel-go-sdk/packages/pagination"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
// FakeBrowserPoolsService is a configurable fake implementing BrowserPoolsService.
14+
type FakeBrowserPoolsService struct {
15+
AcquireFunc func(ctx context.Context, id string, body kernel.BrowserPoolAcquireParams, opts ...option.RequestOption) (*kernel.BrowserPoolAcquireResponse, error)
16+
ListFunc func(ctx context.Context, query kernel.BrowserPoolListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.BrowserPool], error)
17+
}
18+
19+
func (f *FakeBrowserPoolsService) List(ctx context.Context, query kernel.BrowserPoolListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.BrowserPool], error) {
20+
if f.ListFunc != nil {
21+
return f.ListFunc(ctx, query, opts...)
22+
}
23+
return &pagination.OffsetPagination[kernel.BrowserPool]{Items: []kernel.BrowserPool{}}, nil
24+
}
25+
26+
func (f *FakeBrowserPoolsService) New(ctx context.Context, body kernel.BrowserPoolNewParams, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
27+
return &kernel.BrowserPool{}, nil
28+
}
29+
30+
func (f *FakeBrowserPoolsService) Get(ctx context.Context, id string, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
31+
return &kernel.BrowserPool{}, nil
32+
}
33+
34+
func (f *FakeBrowserPoolsService) Update(ctx context.Context, id string, body kernel.BrowserPoolUpdateParams, opts ...option.RequestOption) (*kernel.BrowserPool, error) {
35+
return &kernel.BrowserPool{}, nil
36+
}
37+
38+
func (f *FakeBrowserPoolsService) Delete(ctx context.Context, id string, body kernel.BrowserPoolDeleteParams, opts ...option.RequestOption) error {
39+
return nil
40+
}
41+
42+
func (f *FakeBrowserPoolsService) Acquire(ctx context.Context, id string, body kernel.BrowserPoolAcquireParams, opts ...option.RequestOption) (*kernel.BrowserPoolAcquireResponse, error) {
43+
if f.AcquireFunc != nil {
44+
return f.AcquireFunc(ctx, id, body, opts...)
45+
}
46+
return &kernel.BrowserPoolAcquireResponse{}, nil
47+
}
48+
49+
func (f *FakeBrowserPoolsService) Release(ctx context.Context, id string, body kernel.BrowserPoolReleaseParams, opts ...option.RequestOption) error {
50+
return nil
51+
}
52+
53+
func (f *FakeBrowserPoolsService) Flush(ctx context.Context, id string, opts ...option.RequestOption) error {
54+
return nil
55+
}
56+
57+
func TestBrowserPoolsAcquire_WithNameAndTags(t *testing.T) {
58+
setupStdoutCapture(t)
59+
60+
var capturedID string
61+
var captured kernel.BrowserPoolAcquireParams
62+
fake := &FakeBrowserPoolsService{
63+
AcquireFunc: func(ctx context.Context, id string, body kernel.BrowserPoolAcquireParams, opts ...option.RequestOption) (*kernel.BrowserPoolAcquireResponse, error) {
64+
capturedID = id
65+
captured = body
66+
return &kernel.BrowserPoolAcquireResponse{
67+
SessionID: "sess-acq",
68+
CdpWsURL: "ws://cdp-acq",
69+
Name: "lease-name",
70+
Tags: kernel.Tags{"env": "prod"},
71+
}, nil
72+
},
73+
}
74+
75+
c := BrowserPoolsCmd{client: fake}
76+
err := c.Acquire(context.Background(), BrowserPoolsAcquireInput{
77+
IDOrName: "my-pool",
78+
Name: "lease-name",
79+
Tags: map[string]string{"env": "prod"},
80+
})
81+
assert.NoError(t, err)
82+
83+
// Pool lookup is by id or name; name + tags are forwarded per-lease.
84+
assert.Equal(t, "my-pool", capturedID)
85+
assert.True(t, captured.Name.Valid())
86+
assert.Equal(t, "lease-name", captured.Name.Value)
87+
assert.Equal(t, "prod", captured.Tags["env"])
88+
89+
// And surfaced in the acquired-session table.
90+
out := outBuf.String()
91+
assert.Contains(t, out, "lease-name")
92+
assert.Contains(t, out, "Tags")
93+
assert.Contains(t, out, "env=prod")
94+
}
95+
96+
func TestBrowserPoolsList_ForwardsLimitOffset(t *testing.T) {
97+
setupStdoutCapture(t)
98+
99+
var captured kernel.BrowserPoolListParams
100+
fake := &FakeBrowserPoolsService{
101+
ListFunc: func(ctx context.Context, query kernel.BrowserPoolListParams, opts ...option.RequestOption) (*pagination.OffsetPagination[kernel.BrowserPool], error) {
102+
captured = query
103+
return &pagination.OffsetPagination[kernel.BrowserPool]{Items: []kernel.BrowserPool{}}, nil
104+
},
105+
}
106+
107+
c := BrowserPoolsCmd{client: fake}
108+
err := c.List(context.Background(), BrowserPoolsListInput{Limit: 4, Offset: 8})
109+
110+
assert.NoError(t, err)
111+
assert.Equal(t, int64(4), captured.Limit.Value)
112+
assert.Equal(t, int64(8), captured.Offset.Value)
113+
}
114+
115+
// TestBuildAcquireParams covers the shared name/tags/timeout forwarding used by
116+
// both `browser-pools acquire` and the `browsers create --pool-id` lease path.
117+
func TestBuildAcquireParams(t *testing.T) {
118+
p := buildAcquireParams("lease", map[string]string{"env": "prod"}, 30)
119+
assert.True(t, p.Name.Valid())
120+
assert.Equal(t, "lease", p.Name.Value)
121+
assert.Equal(t, "prod", p.Tags["env"])
122+
assert.True(t, p.AcquireTimeoutSeconds.Valid())
123+
assert.Equal(t, int64(30), p.AcquireTimeoutSeconds.Value)
124+
125+
// Unset inputs produce an empty params struct (nothing forwarded).
126+
empty := buildAcquireParams("", nil, 0)
127+
assert.False(t, empty.Name.Valid())
128+
assert.Len(t, empty.Tags, 0)
129+
assert.False(t, empty.AcquireTimeoutSeconds.Valid())
130+
}

0 commit comments

Comments
 (0)