@pippit-dev/cli 1.0.54 → 1.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/checksums.txt +6 -6
- package/cmd/ip.go +28 -0
- package/cmd/ip_test.go +101 -0
- package/cmd/root.go +1 -0
- package/cmd/space.go +1 -1
- package/dist/checksums.txt +6 -6
- package/internal/commandnames/names.go +1 -0
- package/internal/common/client_ip.go +58 -0
- package/internal/common/client_ip_test.go +135 -0
- package/internal/config/config.go +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,14 @@ pippit-tool-cli get-credit-balance --team-id 0 # 仅本次使用个人
|
|
|
77
77
|
|
|
78
78
|
`space list/use TEAM_ID` 依赖 API 部署 `/api/biz/v1/skill/get_lite_team_list` 的 AK 适配入口,业务团队使用还依赖对应 API 团队权限实现;不直连 commerce Web 接口。宿主 Agent 的选择、任务恢复与不回退规则已写入三个 Skill 的 `Space Selection` 章节。
|
|
79
79
|
|
|
80
|
+
## 查询出口 IP
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pippit-tool-cli ip
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
输出 `{"client_ip":"..."}`,返回服务端看到的出口 IP,无需登录或 AK;沿用 `config.toml` 的 API 地址与 PPE 配置。AK 的 IP 白名单仍在网页密钥管理页设置。
|
|
87
|
+
|
|
80
88
|
## 账号 AIGC 水印设置
|
|
81
89
|
|
|
82
90
|
```bash
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
99a7ec8c86f332f9a3a8e7538d9eb1c237e0d8eebdacdaeb262f77ea716d3bd7 pippit-tool-cli-1.0.55-darwin-amd64.tar.gz
|
|
2
|
+
8cefadb8e6ac41a7c6ff2f1a1ab89448176734ff449238cbcd3f38bbc0acc8cf pippit-tool-cli-1.0.55-darwin-arm64.tar.gz
|
|
3
|
+
69d0d83c74c95b46f1eb859ed5a9fe4aaf3b38f25465ff5a4d0c0bf33d815988 pippit-tool-cli-1.0.55-linux-amd64.tar.gz
|
|
4
|
+
086db5207fbe14e15fb97f0a3c970c36de06e67d333cd68373be6a0e5af2626a pippit-tool-cli-1.0.55-linux-arm64.tar.gz
|
|
5
|
+
14cda14f712ff2c09566fbeb68f39296b200e3d54cdc659e13baf1e9f9b1ed92 pippit-tool-cli-1.0.55-windows-amd64.zip
|
|
6
|
+
86ae993c8b32c1a6a09c81882e21bac7e8368468fe28b1e7be90b68466d8fe23 pippit-tool-cli-1.0.55-windows-arm64.zip
|
package/cmd/ip.go
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"io"
|
|
5
|
+
|
|
6
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
7
|
+
"github.com/Pippit-dev/pippit-cli/internal/commandnames"
|
|
8
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
9
|
+
"github.com/spf13/cobra"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func newIPCommand(stdout, stderr io.Writer, runner *common.Runner, policy adapter.HTTPAdapter) *cobra.Command {
|
|
13
|
+
cmd := &cobra.Command{
|
|
14
|
+
Use: commandnames.IP,
|
|
15
|
+
Short: "Query the outbound IP observed by the server",
|
|
16
|
+
Args: cobra.NoArgs,
|
|
17
|
+
RunE: withErrorLog(commandnames.IP, nil, func(cmd *cobra.Command, _ []string) error {
|
|
18
|
+
result, err := common.GetClientIP(cmd.Context(), runner, policy)
|
|
19
|
+
if err != nil {
|
|
20
|
+
return err
|
|
21
|
+
}
|
|
22
|
+
return common.WriteJSON(stdout, result)
|
|
23
|
+
}),
|
|
24
|
+
}
|
|
25
|
+
cmd.SetOut(stdout)
|
|
26
|
+
cmd.SetErr(stderr)
|
|
27
|
+
return cmd
|
|
28
|
+
}
|
package/cmd/ip_test.go
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"errors"
|
|
8
|
+
"io"
|
|
9
|
+
"net/http"
|
|
10
|
+
"net/http/httptest"
|
|
11
|
+
"strings"
|
|
12
|
+
"testing"
|
|
13
|
+
"time"
|
|
14
|
+
|
|
15
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
16
|
+
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
17
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
18
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
type ipCredentialStore struct {
|
|
22
|
+
auth.CredentialStore
|
|
23
|
+
loads int
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (store *ipCredentialStore) Load(context.Context) (*auth.Credential, error) {
|
|
27
|
+
store.loads++
|
|
28
|
+
return nil, errors.New("corrupt credential fixture")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ipCommandPolicy struct {
|
|
32
|
+
adapter.Default
|
|
33
|
+
options adapter.CLIOptions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func (policy ipCommandPolicy) CLIOptions() adapter.CLIOptions { return policy.options }
|
|
37
|
+
|
|
38
|
+
func TestIPCommandDoesNotReadCredentialsOrInitializeSpace(t *testing.T) {
|
|
39
|
+
for _, accessKey := range []string{"", "existing-key"} {
|
|
40
|
+
t.Run("configured_key="+accessKey, func(t *testing.T) {
|
|
41
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
42
|
+
for _, header := range []string{"Authorization", "Cookie", "X-Team-Id"} {
|
|
43
|
+
if r.Header.Get(header) != "" {
|
|
44
|
+
t.Errorf("anonymous command sent %s", header)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
_, _ = io.WriteString(w, `{"ret":"0","data":{"client_ip":"8.8.8.8"}}`)
|
|
48
|
+
}))
|
|
49
|
+
defer server.Close()
|
|
50
|
+
cfg := config.Load()
|
|
51
|
+
cfg.BaseURL, cfg.AccessKey = server.URL, accessKey
|
|
52
|
+
cfg.HTTPTimeout = time.Second
|
|
53
|
+
policy := ipCommandPolicy{options: adapter.CLIOptions{
|
|
54
|
+
DefaultAllowRootCommands: true,
|
|
55
|
+
LocalCredentialsEnabled: true,
|
|
56
|
+
}}
|
|
57
|
+
runner := newRootRunnerWithAdapter(cfg, policy)
|
|
58
|
+
store := &ipCredentialStore{}
|
|
59
|
+
runner.Auth = auth.NewManager(cfg, auth.WithCredentialStore(store))
|
|
60
|
+
var stdout, stderr bytes.Buffer
|
|
61
|
+
root := newRootCommandWithAdapter(&stdout, &stderr, runner, policy)
|
|
62
|
+
root.SetArgs([]string{"--team-id", "42", "ip"})
|
|
63
|
+
if err := root.Execute(); err != nil {
|
|
64
|
+
t.Fatalf("Execute() = %v, stderr = %q", err, stderr.String())
|
|
65
|
+
}
|
|
66
|
+
if store.loads != 0 {
|
|
67
|
+
t.Fatalf("credential loads = %d, want 0 even with corrupt credentials", store.loads)
|
|
68
|
+
}
|
|
69
|
+
command, _, err := root.Find([]string{"ip"})
|
|
70
|
+
if err != nil || command == nil || common.SpaceSession(command.Context()) != nil {
|
|
71
|
+
t.Fatalf("ip initialized a space session: %v", err)
|
|
72
|
+
}
|
|
73
|
+
var output map[string]string
|
|
74
|
+
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil || len(output) != 1 || output["client_ip"] != "8.8.8.8" {
|
|
75
|
+
t.Fatalf("stdout = %q, error = %v, want only client_ip", stdout.String(), err)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
func TestIPCommandRespectsAdapterFiltering(t *testing.T) {
|
|
82
|
+
for _, allowed := range []bool{true, false} {
|
|
83
|
+
options := adapter.CLIOptions{}
|
|
84
|
+
if allowed {
|
|
85
|
+
options.AllowedRootCommands = []string{"ip"}
|
|
86
|
+
}
|
|
87
|
+
root := NewRootCommandWithAdapter(io.Discard, io.Discard, ipCommandPolicy{options: options})
|
|
88
|
+
command, _, _ := root.Find([]string{"ip"})
|
|
89
|
+
if got := command != nil && command.Name() == "ip"; got != allowed {
|
|
90
|
+
t.Fatalf("ip present = %v, want %v", got, allowed)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func TestIPCommandRejectsArguments(t *testing.T) {
|
|
96
|
+
command := newIPCommand(io.Discard, io.Discard, nil, adapter.Default{})
|
|
97
|
+
command.SetArgs([]string{"unexpected"})
|
|
98
|
+
if err := command.Execute(); err == nil || !strings.Contains(err.Error(), "unexpected") {
|
|
99
|
+
t.Fatalf("Execute() = %v, want positional argument rejection", err)
|
|
100
|
+
}
|
|
101
|
+
}
|
package/cmd/root.go
CHANGED
|
@@ -120,6 +120,7 @@ func newRootCommandWithAdapter(stdout, stderr io.Writer, runner *common.Runner,
|
|
|
120
120
|
root.AddCommand(newDownloadResultCommand(stdout, stderr, runner))
|
|
121
121
|
root.AddCommand(newGetCreditBalanceCommand(stdout, stderr, runner))
|
|
122
122
|
root.AddCommand(newWatermarkCommand(stdout, stderr, runner))
|
|
123
|
+
root.AddCommand(newIPCommand(stdout, stderr, runner, policy))
|
|
123
124
|
root.AddCommand(newModelCommand(stdout, stderr, runner))
|
|
124
125
|
root.AddCommand(newMarketingCommand(stdout, stderr, runner, policy))
|
|
125
126
|
root.AddCommand(newGetThreadCommand(stdout, stderr, runner))
|
package/cmd/space.go
CHANGED
|
@@ -30,7 +30,7 @@ func prepareCommandSpace(cmd *cobra.Command, runner *common.Runner, stderr io.Wr
|
|
|
30
30
|
top = top.Parent()
|
|
31
31
|
}
|
|
32
32
|
switch top.Name() {
|
|
33
|
-
case commandnames.Space, commandnames.Login, commandnames.Logout, commandnames.Status, commandnames.Update, commandnames.Completion, commandnames.Help, commandnames.DownloadResult, commandnames.Watermark:
|
|
33
|
+
case commandnames.Space, commandnames.Login, commandnames.Logout, commandnames.Status, commandnames.Update, commandnames.Completion, commandnames.Help, commandnames.DownloadResult, commandnames.Watermark, commandnames.IP:
|
|
34
34
|
return nil
|
|
35
35
|
}
|
|
36
36
|
raw, _ := cmd.Flags().GetString("space-context")
|
package/dist/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
99a7ec8c86f332f9a3a8e7538d9eb1c237e0d8eebdacdaeb262f77ea716d3bd7 pippit-tool-cli-1.0.55-darwin-amd64.tar.gz
|
|
2
|
+
8cefadb8e6ac41a7c6ff2f1a1ab89448176734ff449238cbcd3f38bbc0acc8cf pippit-tool-cli-1.0.55-darwin-arm64.tar.gz
|
|
3
|
+
69d0d83c74c95b46f1eb859ed5a9fe4aaf3b38f25465ff5a4d0c0bf33d815988 pippit-tool-cli-1.0.55-linux-amd64.tar.gz
|
|
4
|
+
086db5207fbe14e15fb97f0a3c970c36de06e67d333cd68373be6a0e5af2626a pippit-tool-cli-1.0.55-linux-arm64.tar.gz
|
|
5
|
+
14cda14f712ff2c09566fbeb68f39296b200e3d54cdc659e13baf1e9f9b1ed92 pippit-tool-cli-1.0.55-windows-amd64.zip
|
|
6
|
+
86ae993c8b32c1a6a09c81882e21bac7e8368468fe28b1e7be90b68466d8fe23 pippit-tool-cli-1.0.55-windows-arm64.zip
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"net/http"
|
|
7
|
+
"net/netip"
|
|
8
|
+
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
10
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
type ClientIPResult struct {
|
|
14
|
+
ClientIP string `json:"client_ip"`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type clientIPRequestPolicy struct {
|
|
18
|
+
adapter.HTTPAdapter
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func (clientIPRequestPolicy) PrepareAPIRequest(context.Context, *http.Request) (adapter.AuthDecision, error) {
|
|
22
|
+
return adapter.UseDefaultAuth, nil
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// GetClientIP keeps the host transport and routing, without resolving account credentials.
|
|
26
|
+
func GetClientIP(ctx context.Context, runner *Runner, policy adapter.HTTPAdapter) (*ClientIPResult, error) {
|
|
27
|
+
if runner == nil || runner.Config == nil {
|
|
28
|
+
return nil, fmt.Errorf("查询出口 IP 的运行配置缺失")
|
|
29
|
+
}
|
|
30
|
+
cfg := runner.Config
|
|
31
|
+
anonymousPolicy := clientIPRequestPolicy{HTTPAdapter: adapter.HTTPOrDefault(policy)}
|
|
32
|
+
client := NewHTTPClientWithAdapter(cfg.BaseURL, cfg.HTTPTimeout, NewAccessKeyAuthorizer(""), adapter.WithPPE(anonymousPolicy, cfg.PPEEnv))
|
|
33
|
+
// This query is anonymous even when the caller's context carries a selected team.
|
|
34
|
+
ctx = WithSpace(ctx, nil)
|
|
35
|
+
var resp struct {
|
|
36
|
+
Ret string `json:"ret"`
|
|
37
|
+
Errmsg string `json:"errmsg"`
|
|
38
|
+
LogID string `json:"log_id"`
|
|
39
|
+
Data *ClientIPResult `json:"data"`
|
|
40
|
+
}
|
|
41
|
+
if err := client.SendRequest(ctx, config.GetClientIPPath, nil, &resp); err != nil {
|
|
42
|
+
return nil, fmt.Errorf("查询出口 IP 请求失败: %w", err)
|
|
43
|
+
}
|
|
44
|
+
if resp.Ret != "0" {
|
|
45
|
+
if resp.Errmsg == "" {
|
|
46
|
+
resp.Errmsg = "未知错误"
|
|
47
|
+
}
|
|
48
|
+
return nil, NewLogIDError(fmt.Sprintf("查询出口 IP 请求返回失败: ret=%s errmsg=%s", resp.Ret, resp.Errmsg), resp.LogID)
|
|
49
|
+
}
|
|
50
|
+
if resp.Data == nil || resp.Data.ClientIP == "" {
|
|
51
|
+
return nil, NewLogIDError("查询出口 IP 响应缺少 data.client_ip", resp.LogID)
|
|
52
|
+
}
|
|
53
|
+
ip, err := netip.ParseAddr(resp.Data.ClientIP)
|
|
54
|
+
if err != nil || ip.Zone() != "" {
|
|
55
|
+
return nil, NewLogIDError("查询出口 IP 响应的 data.client_ip 无效", resp.LogID)
|
|
56
|
+
}
|
|
57
|
+
return &ClientIPResult{ClientIP: ip.Unmap().String()}, nil
|
|
58
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"io"
|
|
6
|
+
"net/http"
|
|
7
|
+
"net/http/httptest"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"github.com/Pippit-dev/pippit-cli/internal/adapter"
|
|
13
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
14
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type clientIPPolicy struct {
|
|
18
|
+
adapter.Default
|
|
19
|
+
prepareCalls int
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func (p *clientIPPolicy) PrepareAPIRequest(_ context.Context, req *http.Request) (adapter.AuthDecision, error) {
|
|
23
|
+
p.prepareCalls++
|
|
24
|
+
req.Header.Set("Authorization", "Bearer adapter-key")
|
|
25
|
+
req.Header.Set("Cookie", "sessionid=adapter-cookie")
|
|
26
|
+
req.Header.Set("X-Team-Id", "99")
|
|
27
|
+
return adapter.AuthProvided, nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func (*clientIPPolicy) WrapTransport(next http.RoundTripper) http.RoundTripper {
|
|
31
|
+
if next == nil {
|
|
32
|
+
next = http.DefaultTransport
|
|
33
|
+
}
|
|
34
|
+
return clientIPTransport{next: next}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type clientIPTransport struct{ next http.RoundTripper }
|
|
38
|
+
|
|
39
|
+
func (transport clientIPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
40
|
+
request := req.Clone(req.Context())
|
|
41
|
+
request.Header.Set("X-Test-IP-Transport", "present")
|
|
42
|
+
return transport.next.RoundTrip(request)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func TestGetClientIPIsAnonymousAndPreservesTransportAndRouting(t *testing.T) {
|
|
46
|
+
for _, env := range []string{"", "ppe_first", "ppe_second"} {
|
|
47
|
+
t.Run("env="+env, func(t *testing.T) {
|
|
48
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
49
|
+
if r.Method != http.MethodGet || r.URL.Path != "/api/biz/v1/skill/get_client_ip" {
|
|
50
|
+
t.Errorf("request = %s %s, want anonymous client IP GET", r.Method, r.URL.Path)
|
|
51
|
+
}
|
|
52
|
+
body, err := io.ReadAll(r.Body)
|
|
53
|
+
if err != nil || len(body) != 0 || r.Header.Get("Content-Type") != "" {
|
|
54
|
+
t.Errorf("GET body = %q, Content-Type = %q, error = %v", body, r.Header.Get("Content-Type"), err)
|
|
55
|
+
}
|
|
56
|
+
for _, header := range []string{"Authorization", "Cookie", "X-Team-Id"} {
|
|
57
|
+
if r.Header.Get(header) != "" {
|
|
58
|
+
t.Errorf("anonymous request retained %s", header)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if r.Header.Get("X-Test-IP-Transport") != "present" {
|
|
62
|
+
t.Error("anonymous request bypassed adapter transport")
|
|
63
|
+
}
|
|
64
|
+
wantUsePPE := ""
|
|
65
|
+
if env != "" {
|
|
66
|
+
wantUsePPE = "1"
|
|
67
|
+
}
|
|
68
|
+
if r.Header.Get("x-tt-env") != env || r.Header.Get("x-use-ppe") != wantUsePPE {
|
|
69
|
+
t.Error("anonymous request did not follow the configured PPE environment")
|
|
70
|
+
}
|
|
71
|
+
_, _ = io.WriteString(w, `{"ret":"0","data":{"client_ip":"8.8.8.8"}}`)
|
|
72
|
+
}))
|
|
73
|
+
defer server.Close()
|
|
74
|
+
|
|
75
|
+
cfg := config.Load()
|
|
76
|
+
cfg.BaseURL, cfg.PPEEnv, cfg.AccessKey = server.URL, env, "existing-key"
|
|
77
|
+
cfg.HTTPTimeout = time.Second
|
|
78
|
+
policy := &clientIPPolicy{}
|
|
79
|
+
runner := NewRunner(cfg, NewHTTPClientWithAdapter(server.URL, time.Second, NewAccessKeyAuthorizer(cfg.AccessKey), policy))
|
|
80
|
+
teamID := "42"
|
|
81
|
+
ctx := WithSpace(context.Background(), &space.Session{Explicit: &teamID})
|
|
82
|
+
result, err := GetClientIP(ctx, runner, policy)
|
|
83
|
+
if err != nil || result == nil || result.ClientIP != "8.8.8.8" {
|
|
84
|
+
t.Fatalf("GetClientIP() = %#v, %v", result, err)
|
|
85
|
+
}
|
|
86
|
+
if policy.prepareCalls != 0 {
|
|
87
|
+
t.Fatalf("adapter authentication calls = %d, want 0", policy.prepareCalls)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func TestGetClientIPRejectsUnusableResponses(t *testing.T) {
|
|
94
|
+
for _, tc := range []struct {
|
|
95
|
+
name, response, wantError string
|
|
96
|
+
status int
|
|
97
|
+
}{
|
|
98
|
+
{"unavailable_source", `{"ret":"53001","errmsg":"暂时无法识别当前来源 IP","log_id":"log_ip"}`, "53001", http.StatusOK},
|
|
99
|
+
{"unavailable_source_http503", `{"ret":"53001","errmsg":"暂时无法识别当前来源 IP","log_id":"log_ip"}`, "53001", http.StatusServiceUnavailable},
|
|
100
|
+
{"missing_data", `{"ret":"0","log_id":"log_ip"}`, "data", http.StatusOK},
|
|
101
|
+
{"missing_ip", `{"ret":"0","log_id":"log_ip","data":{}}`, "client_ip", http.StatusOK},
|
|
102
|
+
{"invalid_ip", `{"ret":"0","log_id":"log_ip","data":{"client_ip":"not-an-ip"}}`, "client_ip", http.StatusOK},
|
|
103
|
+
{"zoned_ip", `{"ret":"0","log_id":"log_ip","data":{"client_ip":"fe80::1%eth0"}}`, "client_ip", http.StatusOK},
|
|
104
|
+
} {
|
|
105
|
+
t.Run(tc.name, func(t *testing.T) {
|
|
106
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
107
|
+
w.WriteHeader(tc.status)
|
|
108
|
+
_, _ = io.WriteString(w, tc.response)
|
|
109
|
+
}))
|
|
110
|
+
defer server.Close()
|
|
111
|
+
cfg := config.Load()
|
|
112
|
+
cfg.BaseURL = server.URL
|
|
113
|
+
cfg.HTTPTimeout = time.Second
|
|
114
|
+
_, err := GetClientIP(context.Background(), NewRunner(cfg, nil), adapter.Default{})
|
|
115
|
+
if err == nil || !strings.Contains(err.Error(), tc.wantError) || !strings.Contains(err.Error(), "log_ip") {
|
|
116
|
+
t.Fatalf("GetClientIP() error = %v, want %q and log ID", err, tc.wantError)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func TestGetClientIPSupportsIPv6(t *testing.T) {
|
|
123
|
+
const ip = "2001:4860:4860::8888"
|
|
124
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
125
|
+
_, _ = io.WriteString(w, `{"ret":"0","data":{"client_ip":"`+ip+`"}}`)
|
|
126
|
+
}))
|
|
127
|
+
defer server.Close()
|
|
128
|
+
cfg := config.Load()
|
|
129
|
+
cfg.BaseURL = server.URL
|
|
130
|
+
cfg.HTTPTimeout = time.Second
|
|
131
|
+
result, err := GetClientIP(context.Background(), NewRunner(cfg, nil), adapter.Default{})
|
|
132
|
+
if err != nil || result == nil || result.ClientIP != ip {
|
|
133
|
+
t.Fatalf("GetClientIP() = %#v, %v, want IPv6 %s", result, err, ip)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -21,6 +21,7 @@ const (
|
|
|
21
21
|
SubmitMarketingRunPath = "/api/biz/v1/agent/submit_marketing_run"
|
|
22
22
|
QueryGenerateVideoResultPath = "/api/biz/v1/agent/query_generate_video_result"
|
|
23
23
|
GetCreditBalancePath = "/api/biz/v1/skill/get_credit_balance"
|
|
24
|
+
GetClientIPPath = "/api/biz/v1/skill/get_client_ip"
|
|
24
25
|
GetWatermarkSettingPath = "/api/biz/v1/skill/mget_user_setting"
|
|
25
26
|
UpdateWatermarkSettingPath = "/api/biz/v1/skill/update_user_setting"
|
|
26
27
|
GetLiteTeamListPath = "/api/biz/v1/skill/get_lite_team_list"
|