Files
cdp_hub/cmd/chub/main_test.go
T

204 lines
8.7 KiB
Go
Raw Normal View History

package main
import (
"bytes"
"context"
"errors"
"strings"
"testing"
"chub/internal/domain"
"chub/internal/platform/browser"
"chub/internal/platform/config"
"chub/internal/ui"
)
func TestRunCLIRejectsUnknownCommandWithStableCode(t *testing.T) {
var out bytes.Buffer
if code := runCLI([]string{"wat"}, &out, &out); code != 2 || !strings.Contains(out.String(), `"invalid_argument"`) {
t.Fatalf("code=%d output=%s", code, out.String())
}
}
func TestInstanceStarterBuildsLaunchSpecFromInstanceAndSettings(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
resolver := &fakeExecutableResolver{path: `C:\Browser\msedge.exe`}
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9666}, inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
starter := instanceStarter{launcher: launcher, resolver: resolver, managedProfiles: fakeManagedProfileInspector{}, externalProfiles: fakeExternalProfileInspector{}, remoteDebug: remote, portAllocator: remote}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "edge-a", Browser: "Edge", UserDataDir: `C:\profiles\edge-a`}, ui.SettingsState{EdgePath: `C:\Configured\msedge.exe`, RemoteDebugStartPort: 9666})
if err != nil || outcome.PID != 4242 || outcome.RemoteDebugPort != 9666 {
t.Fatalf("Start() = %#v, %v", outcome, err)
}
if resolver.kind != domain.BrowserEdge || resolver.configured != `C:\Configured\msedge.exe` {
t.Fatalf("resolver input = %s, %q", resolver.kind, resolver.configured)
}
if launcher.spec.Kind != domain.BrowserEdge || launcher.spec.Executable != resolver.path || launcher.spec.TargetURL != "" || launcher.spec.RemoteDebugPort != 9666 {
t.Fatalf("launch spec = %#v", launcher.spec)
}
}
func TestInstanceStarterAssociatesVerifiedExternalProfileWithoutLaunching(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9668}}
starter := instanceStarter{
launcher: launcher,
resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`},
managedProfiles: fakeManagedProfileInspector{},
externalProfiles: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 16108, Source: browser.ProfileSourceMessageWindow}},
remoteDebug: remote,
portAllocator: remote,
}
outcome, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666})
if err != nil || !outcome.External || outcome.PID != 16108 || outcome.RemoteDebugPort != 9668 || outcome.Source != browser.ProfileSourceMessageWindow {
t.Fatalf("Start() = %#v, %v", outcome, err)
}
if launcher.calls != 0 {
t.Fatalf("external association launched %d processes", launcher.calls)
}
}
func TestInstanceStarterRejectsUnverifiedExternalProfile(t *testing.T) {
launcher := &fakeProcessLauncher{handle: fakeProcessHandle{pid: 4242}}
remote := &fakeRemoteDebugInspector{inspectErr: browser.ErrRemoteDebugEndpointUnavailable}
starter := instanceStarter{
launcher: launcher,
resolver: &fakeExecutableResolver{path: `C:\Browser\chrome.exe`},
managedProfiles: fakeManagedProfileInspector{},
externalProfiles: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 16108, Source: browser.ProfileSourceLock}},
remoteDebug: remote,
portAllocator: remote,
}
_, err := starter.Start(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}, ui.SettingsState{RemoteDebugStartPort: 9666})
if !errors.Is(err, domain.ErrProfileOccupied) {
t.Fatalf("Start() error = %v", err)
}
if launcher.calls != 0 {
t.Fatalf("unverified external profile launched %d processes", launcher.calls)
}
}
func TestMergeInstanceConfigPreservesExistingLaunchOptions(t *testing.T) {
existing := []config.Instance{{ID: "one", Name: "旧名称", Launch: domain.LaunchSpec{Kind: domain.BrowserChrome, UserDataDir: `C:\profiles\old`, ProfileDirectory: "Default", ProxyServer: "https://proxy.local:8443", ExtraArgs: []string{"--window-size=1280,900"}}}}
updated := mergeInstanceConfig(existing, []ui.InstanceRow{{ID: "one", Name: "新名称", Browser: "Edge", UserDataDir: `C:\profiles\new`, TargetURL: "https://example.com"}})
if len(updated) != 1 {
t.Fatalf("instances = %d, want 1", len(updated))
}
got := updated[0]
if got.Name != "新名称" || got.Launch.Kind != domain.BrowserEdge || got.Launch.UserDataDir != `C:\profiles\new` || got.Launch.TargetURL != "https://example.com" {
t.Fatalf("updated instance = %#v", got)
}
if got.Launch.ProfileDirectory != "Default" || got.Launch.ProxyServer != "https://proxy.local:8443" || len(got.Launch.ExtraArgs) != 1 {
t.Fatalf("existing launch options were lost: %#v", got.Launch)
}
}
2026-07-25 16:36:26 +08:00
func TestInstanceStatusRefresherChecksOnlyConfiguredRows(t *testing.T) {
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9777}}
refresher := instanceStatusRefresher{
managedProfiles: fakeManagedProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 5432, Source: "chub_registry"}},
externalProfiles: fakeExternalProfileInspector{},
remoteDebug: remote,
}
rows := []ui.InstanceRow{{ID: "managed", Browser: "Chrome", UserDataDir: `C:\profiles\managed`, RemoteDebugPort: 9777}}
results, err := refresher.Refresh(context.Background(), rows)
if err != nil || len(results) != 1 {
t.Fatalf("Refresh() = %#v, %v", results, err)
}
got := results[0]
if got.ID != "managed" || got.Status != "运行中" || got.PID != 5432 || got.RemoteDebugPort != 9777 || got.OccupancySource != "chub_registry" {
t.Fatalf("refresh result = %#v", got)
}
}
func TestInstanceStatusRefresherAssociatesExternalWithoutLifecycleControl(t *testing.T) {
remote := &fakeRemoteDebugInspector{endpoint: browser.RemoteDebugEndpoint{Port: 9668}}
refresher := instanceStatusRefresher{
managedProfiles: fakeManagedProfileInspector{},
externalProfiles: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 16108, Source: browser.ProfileSourceMessageWindow}},
remoteDebug: remote,
}
results, err := refresher.Refresh(context.Background(), []ui.InstanceRow{{ID: "external", Browser: "Edge", UserDataDir: `C:\profiles\external`}})
if err != nil || len(results) != 1 {
t.Fatalf("Refresh() = %#v, %v", results, err)
}
got := results[0]
if got.Status != "外部已关联" || got.PID != 16108 || got.RemoteDebugPort != 9668 || got.OccupancySource != browser.ProfileSourceMessageWindow {
t.Fatalf("external refresh result = %#v", got)
}
}
type fakeExecutableResolver struct {
path string
kind domain.BrowserKind
configured string
}
func (r *fakeExecutableResolver) Resolve(_ context.Context, kind domain.BrowserKind, configured string) (string, error) {
r.kind = kind
r.configured = configured
return r.path, nil
}
type fakeProcessLauncher struct {
spec domain.LaunchSpec
handle browser.ProcessHandle
calls int
}
func (l *fakeProcessLauncher) Start(_ context.Context, spec domain.LaunchSpec) (browser.ProcessHandle, error) {
l.spec = spec
l.calls++
return l.handle, nil
}
type fakeProcessHandle struct{ pid int }
func (p fakeProcessHandle) PID() int { return p.pid }
func (fakeProcessHandle) Wait(context.Context) (int, error) { return 0, nil }
type fakeManagedProfileInspector struct{ use browser.ProfileUse }
func (i fakeManagedProfileInspector) InspectProfile(context.Context, string) (browser.ProfileUse, error) {
return i.use, nil
}
type fakeExternalProfileInspector struct {
use browser.ProfileUse
err error
}
func (i fakeExternalProfileInspector) InspectProfile(context.Context, domain.BrowserKind, string, int) (browser.ProfileUse, error) {
return i.use, i.err
}
type fakeRemoteDebugInspector struct {
endpoint browser.RemoteDebugEndpoint
inspectErr error
waitErr error
port int
}
func (i *fakeRemoteDebugInspector) InspectRemoteDebugEndpoint(context.Context, domain.BrowserKind, string) (browser.RemoteDebugEndpoint, error) {
return i.endpoint, i.inspectErr
}
func (i *fakeRemoteDebugInspector) WaitForRemoteDebugEndpoint(context.Context, domain.BrowserKind, string) (browser.RemoteDebugEndpoint, error) {
return i.endpoint, i.waitErr
}
func (i *fakeRemoteDebugInspector) InspectRemoteDebugPort(context.Context, domain.BrowserKind, int) (browser.RemoteDebugEndpoint, error) {
return i.endpoint, i.inspectErr
}
func (i *fakeRemoteDebugInspector) WaitForRemoteDebugPort(context.Context, domain.BrowserKind, int) (browser.RemoteDebugEndpoint, error) {
return i.endpoint, i.waitErr
}
func (i *fakeRemoteDebugInspector) FindAvailableRemoteDebugPort(_ context.Context, start int) (int, error) {
if i.port != 0 {
return i.port, nil
}
return start, nil
}