Files
Ant-Browser/backend/test/launchcode/server_profile_manage_test.go
T
Ant Browser Release Bot 3d264eb83d publish: 1.1.0 snapshot (a358335)
channel: master
version: 1.1.0
source-ref: master
published-at-utc: 2026-03-29T11:32:04Z
2026-03-29 19:32:05 +08:00

329 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package launchcode_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"ant-chrome/backend/internal/browser"
"ant-chrome/backend/internal/config"
)
func TestListProfilesAPIIncludesLaunchCodes(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, func(cfg *config.Config) {
cfg.Browser.Proxies = []config.BrowserProxy{
{
ProxyId: "proxy-us",
ProxyName: "US Residential",
ProxyConfig: "socks5://127.0.0.1:1080",
},
}
})
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
first, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-a",
ProxyId: "proxy-us",
Tags: []string{"电商"},
Keywords: []string{"buyer-a"},
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
second, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-b",
ProxyConfig: "http://127.0.0.1:8080",
Keywords: []string{"buyer-b"},
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
if _, err := svc.SetCode(first.ProfileId, "BUYER_A"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/profiles", nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("期望 200,实际 %dbody=%s", w.Code, w.Body.String())
}
var resp struct {
OK bool `json:"ok"`
Count int `json:"count"`
Items []browser.Profile `json:"items"`
}
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("解析响应失败: %v", err)
}
if !resp.OK || resp.Count != 2 || len(resp.Items) != 2 {
t.Fatalf("列表响应错误: %+v", resp)
}
seen := make(map[string]browser.Profile, len(resp.Items))
for _, item := range resp.Items {
if item.LaunchCode == "" {
t.Fatalf("列表应返回 launchCode: %+v", resp.Items)
}
seen[item.ProfileId] = item
}
if _, ok := seen[first.ProfileId]; !ok {
t.Fatalf("列表缺少第一个实例: %+v", resp.Items)
}
if _, ok := seen[second.ProfileId]; !ok {
t.Fatalf("列表缺少第二个实例: %+v", resp.Items)
}
}
func TestGetProfileAPIReturnsProfileByID(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, nil)
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
profile, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-get",
ProxyConfig: "http://127.0.0.1:8080",
Tags: []string{"北美"},
Keywords: []string{"buyer-get"},
GroupId: "group-get",
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
if _, err := svc.SetCode(profile.ProfileId, "BUYER_GET"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/profiles/"+profile.ProfileId, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("期望 200,实际 %dbody=%s", w.Code, w.Body.String())
}
var resp struct {
OK bool `json:"ok"`
LaunchCode string `json:"launchCode"`
Profile *browser.Profile `json:"profile"`
}
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("解析响应失败: %v", err)
}
if !resp.OK || resp.Profile == nil {
t.Fatalf("查询响应错误: %+v", resp)
}
if resp.LaunchCode != "BUYER_GET" || resp.Profile.GroupId != "group-get" {
t.Fatalf("查询字段错误: %+v", resp)
}
}
func TestUpdateProfileAPIUpdatesFieldsAndAutoLaunches(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, func(cfg *config.Config) {
cfg.Browser.Proxies = []config.BrowserProxy{
{
ProxyId: "proxy-us",
ProxyName: "US Residential",
ProxyConfig: "socks5://127.0.0.1:1080",
},
}
})
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
profile, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-old",
ProxyConfig: "http://127.0.0.1:8080",
Keywords: []string{"buyer-old"},
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
if _, err := svc.SetCode(profile.ProfileId, "BUYER_OLD"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
payload := bytes.NewBufferString(`{
"profile": {
"profileName": "buyer-new",
"userDataDir": "buyers/buyer-new",
"proxyId": "proxy-us",
"launchArgs": ["--lang=en-US"],
"tags": ["电商", "北美"],
"keywords": ["buyer-new", "amazon"],
"groupId": "group-sales-us"
},
"launchCode": "BUYER_NEW",
"autoLaunch": true,
"start": {
"launchArgs": ["--window-size=1280,800"],
"startUrls": ["https://example.com/order"],
"skipDefaultStartUrls": true
}
}`)
req := httptest.NewRequest(http.MethodPut, "/api/profiles/"+profile.ProfileId, payload)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("期望 200,实际 %dbody=%s", w.Code, w.Body.String())
}
if len(starter.started) != 1 {
t.Fatalf("更新后应自动启动 1 次,实际 %+v", starter.started)
}
if len(starter.lastParams.StartURLs) != 1 || starter.lastParams.StartURLs[0] != "https://example.com/order" {
t.Fatalf("startUrls 未透传: %+v", starter.lastParams)
}
updated, status, errMsg := handlerProfileSnapshot(t, mgr, svc, profile.ProfileId)
if errMsg != "" || status != http.StatusOK {
t.Fatalf("读取更新后实例失败: status=%d err=%s", status, errMsg)
}
if updated.ProfileName != "buyer-new" || updated.ProxyId != "proxy-us" || updated.ProxyConfig != "socks5://127.0.0.1:1080" {
t.Fatalf("更新未生效: %+v", updated)
}
if updated.GroupId != "group-sales-us" || updated.LaunchCode != "BUYER_NEW" || !updated.Running {
t.Fatalf("更新后的分组/launchCode/运行状态错误: %+v", updated)
}
}
func TestUpdateProfileAPIRollsBackOnDuplicateLaunchCode(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, nil)
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
first, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-first",
ProxyConfig: "http://127.0.0.1:8080",
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
second, err := mgr.Create(browser.ProfileInput{
ProfileName: "buyer-second",
ProxyConfig: "http://127.0.0.1:9090",
})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
if _, err := svc.SetCode(first.ProfileId, "BUYER_FIRST"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
if _, err := svc.SetCode(second.ProfileId, "BUYER_SECOND"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
payload := bytes.NewBufferString(`{
"profile": {
"profileName": "buyer-first-updated",
"proxyConfig": "http://127.0.0.1:10080",
"keywords": ["buyer-first-updated"]
},
"launchCode": "BUYER_SECOND"
}`)
req := httptest.NewRequest(http.MethodPut, "/api/profiles/"+first.ProfileId, payload)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusConflict {
t.Fatalf("期望 409,实际 %dbody=%s", w.Code, w.Body.String())
}
current, status, errMsg := handlerProfileSnapshot(t, mgr, svc, first.ProfileId)
if errMsg != "" || status != http.StatusOK {
t.Fatalf("读取回滚后实例失败: status=%d err=%s", status, errMsg)
}
if current.ProfileName != "buyer-first" || current.ProxyConfig != "http://127.0.0.1:8080" || current.LaunchCode != "BUYER_FIRST" {
t.Fatalf("launchCode 冲突后应回滚更新: %+v", current)
}
}
func TestDeleteProfileAPIRemovesProfileAndLaunchCode(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, nil)
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
profile, err := mgr.Create(browser.ProfileInput{ProfileName: "buyer-delete"})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
if _, err := svc.SetCode(profile.ProfileId, "BUYER_DELETE"); err != nil {
t.Fatalf("设置 launchCode 失败: %v", err)
}
req := httptest.NewRequest(http.MethodDelete, "/api/profiles/"+profile.ProfileId, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("期望 200,实际 %dbody=%s", w.Code, w.Body.String())
}
if _, ok := mgr.Profiles[profile.ProfileId]; ok {
t.Fatalf("实例删除后仍存在于内存: %s", profile.ProfileId)
}
if _, err := svc.Resolve("BUYER_DELETE"); err == nil {
t.Fatal("删除后 launchCode 仍可解析")
}
}
func TestDeleteProfileAPIRejectsRunningProfile(t *testing.T) {
svc := newInMemoryService()
mgr := newProfileCreateTestManager(t, nil)
starter := &managerBackedStarter{mgr: mgr}
handler := buildTestHandlerWithManager(svc, starter, mgr)
profile, err := mgr.Create(browser.ProfileInput{ProfileName: "buyer-running"})
if err != nil {
t.Fatalf("创建测试实例失败: %v", err)
}
mgr.Profiles[profile.ProfileId].Running = true
req := httptest.NewRequest(http.MethodDelete, "/api/profiles/"+profile.ProfileId, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusConflict {
t.Fatalf("期望 409,实际 %dbody=%s", w.Code, w.Body.String())
}
if _, ok := mgr.Profiles[profile.ProfileId]; !ok {
t.Fatalf("运行中实例不应被删除: %s", profile.ProfileId)
}
}
func handlerProfileSnapshot(t *testing.T, mgr *browser.Manager, svc interface {
EnsureCode(profileID string) (string, error)
}, profileID string) (*browser.Profile, int, string) {
t.Helper()
mgr.Mutex.Lock()
profile, ok := mgr.Profiles[profileID]
var snapshot browser.Profile
if ok && profile != nil {
snapshot = *profile
}
mgr.Mutex.Unlock()
if !ok {
return nil, http.StatusNotFound, "profile not found"
}
if snapshot.LaunchCode == "" {
if code, err := svc.EnsureCode(snapshot.ProfileId); err == nil {
snapshot.LaunchCode = code
}
}
return &snapshot, http.StatusOK, ""
}