@pippit-dev/cli 1.0.17 → 1.0.18
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 +23 -2
- package/checksums.txt +6 -6
- package/cmd/auth/auth.go +136 -142
- package/cmd/auth/auth_test.go +134 -0
- package/cmd/canvas/canvas.go +235 -0
- package/cmd/canvas/canvas_test.go +126 -0
- package/cmd/root.go +29 -4
- package/cmd/root_test.go +60 -0
- package/cmd/short_drama_test.go +4 -7
- package/cmd/update/update.go +34 -5
- package/cmd/update/update_test.go +84 -0
- package/internal/auth/auth_test.go +813 -0
- package/internal/auth/browser_darwin.go +14 -0
- package/internal/auth/browser_env.go +35 -0
- package/internal/auth/browser_linux.go +14 -0
- package/internal/auth/browser_windows.go +14 -0
- package/internal/auth/identity.go +89 -0
- package/internal/auth/loopback.go +324 -0
- package/internal/auth/manager.go +338 -144
- package/internal/auth/store.go +303 -0
- package/internal/auth/store_file_unix.go +176 -0
- package/internal/auth/store_file_windows.go +11 -0
- package/internal/auth/types.go +72 -0
- package/internal/canvas/allocate.go +70 -0
- package/internal/canvas/apply.go +203 -0
- package/internal/canvas/canvas_test.go +446 -0
- package/internal/canvas/create.go +380 -0
- package/internal/canvas/get.go +156 -0
- package/internal/canvas/types.go +68 -0
- package/internal/canvas/upload.go +250 -0
- package/internal/common/access_key.go +42 -6
- package/internal/common/access_key_test.go +113 -0
- package/internal/common/client.go +119 -21
- package/internal/common/client_test.go +213 -0
- package/internal/common/runner.go +15 -0
- package/internal/config/config.go +0 -20
- package/internal/config/config_test.go +0 -18
- package/package.json +1 -1
- package/skills/short-drama/SKILL.md +4 -4
- package/skills/xyq-nest-skill/SKILL.md +11 -1
package/README.md
CHANGED
|
@@ -205,7 +205,7 @@ python3 skills/xyq-nest-skill/scripts/download_results.py \
|
|
|
205
205
|
|
|
206
206
|
```bash
|
|
207
207
|
npx @pippit-dev/cli@latest install
|
|
208
|
-
|
|
208
|
+
pippit-tool-cli login
|
|
209
209
|
pippit-tool-cli --version
|
|
210
210
|
pippit-tool-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
|
|
211
211
|
pippit-tool-cli short-drama +upload-file --path ./reference.doc
|
|
@@ -222,6 +222,25 @@ pippit-tool-cli download-result --output-path ./thread_123/results/result.mp4 --
|
|
|
222
222
|
|
|
223
223
|
短剧命令的错误日志会追加写入本地每日日志文件:`~/.pippit_tool_cli/logs/yyyy-mm-dd.log`。日志路径会基于当前用户主目录和系统路径分隔符生成,因此可在 macOS、Linux 和 Windows 上使用。
|
|
224
224
|
|
|
225
|
+
## Canvas 原子命令
|
|
226
|
+
|
|
227
|
+
CLI 提供个人漫剧画布的通用原子命令,不包含特定来源的导入或转换逻辑:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# 首次使用时打开小云雀网页授权
|
|
231
|
+
pippit-tool-cli login
|
|
232
|
+
pippit-tool-cli status
|
|
233
|
+
|
|
234
|
+
# 创建、分配资产 ID、查询、上传与提交单个画布 transaction
|
|
235
|
+
pippit-tool-cli canvas create --title "CLI Canvas" --wait
|
|
236
|
+
pippit-tool-cli canvas allocate --count 3
|
|
237
|
+
pippit-tool-cli canvas get --asset-id PIPPIT_ASSET_ID
|
|
238
|
+
pippit-tool-cli canvas upload --path ./reference.png
|
|
239
|
+
pippit-tool-cli canvas apply --project-id PROJECT_ID --file ./patch.json
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
五个命令均输出单行 JSON,资源 ID 保持字符串。`allocate` 只预留 ID,实际资产仍由后续 `apply` transaction 创建。`create` 的 `request_id` 用于追踪,不是跨服务崩溃窗口的严格幂等键;写请求结果不明确时不要盲目重放,应先使用 `canvas get` 回读确认。`apply` 当前只接受一个 transaction,但该 transaction 可以包含多个 patches;CLI 会严格检查 transaction ACK 和每个目标资产的新版本。
|
|
243
|
+
|
|
225
244
|
## 生图 CLI
|
|
226
245
|
|
|
227
246
|
`generate-image` 会上传本地参考图片,然后向综合 Nest Agent 提交生图请求:
|
|
@@ -333,4 +352,6 @@ pippit-tool-cli query-result \
|
|
|
333
352
|
|
|
334
353
|
## 鉴权
|
|
335
354
|
|
|
336
|
-
`
|
|
355
|
+
原生 CLI 命令通过 `pippit-tool-cli login` 打开小云雀网页授权,并把本机设备专属凭证保存到系统安全凭证库;Access Key 不会显示在终端。可用 `pippit-tool-cli status` 查看状态、`pippit-tool-cli logout` 清除本机登录。
|
|
356
|
+
|
|
357
|
+
CI 或 Agent 可继续显式设置 `XYQ_ACCESS_KEY`,它会覆盖本机网页登录凭证;配置错误时不会静默回退到个人登录。`skills/xyq-nest-skill/scripts` 下的独立 Python 脚本尚未接入原生 CLI 凭证库,当前仍需要该环境变量。
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
34184e1dab803c7c25acb2ef2b95c0358afe39aaf5423140342b5c12e1e80bb3 pippit-tool-cli-1.0.18-darwin-amd64.tar.gz
|
|
2
|
+
0e66993f2b09950e206ee9e9f36c8a8994f6413fffc6a38ef4808457d66eb218 pippit-tool-cli-1.0.18-darwin-arm64.tar.gz
|
|
3
|
+
24dbe1e620a760d7b43636f881e7716e0beefa32f81e45311983fc6a6fc94acf pippit-tool-cli-1.0.18-linux-amd64.tar.gz
|
|
4
|
+
1cdb7cbf054ef1203579cd1c8e71035ce0c434cbfaed7dd3f1bc35605bfedd72 pippit-tool-cli-1.0.18-linux-arm64.tar.gz
|
|
5
|
+
9028a53a9ac03967e245f4e1348bdea82f03dcd30dc85a72c30bcf744ff1bd56 pippit-tool-cli-1.0.18-windows-amd64.zip
|
|
6
|
+
6fe7de9b87de3bdd41f7eefb6514e9c48acd079fdc4ff2c5e917b03d01a8ecc0 pippit-tool-cli-1.0.18-windows-arm64.zip
|
package/cmd/auth/auth.go
CHANGED
|
@@ -1,144 +1,138 @@
|
|
|
1
1
|
package authcmd
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// if err != nil {
|
|
140
|
-
// return err
|
|
141
|
-
// }
|
|
142
|
-
// _, err = fmt.Fprintln(w, string(data))
|
|
143
|
-
// return err
|
|
144
|
-
//}
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"strings"
|
|
9
|
+
"time"
|
|
10
|
+
|
|
11
|
+
internal_auth "github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
12
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
13
|
+
"github.com/spf13/cobra"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
type loginResult struct {
|
|
17
|
+
LoggedIn bool `json:"logged_in"`
|
|
18
|
+
Source string `json:"source"`
|
|
19
|
+
UID string `json:"uid,omitempty"`
|
|
20
|
+
CredentialScope string `json:"credential_scope,omitempty"`
|
|
21
|
+
ExpiresAt string `json:"expires_at,omitempty"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type logoutResult struct {
|
|
25
|
+
LoggedOut bool `json:"logged_out"`
|
|
26
|
+
EnvironmentStillActive bool `json:"environment_still_active,omitempty"`
|
|
27
|
+
RemoteCredentialPreserved bool `json:"remote_credential_preserved"`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// NewLoginCommand creates the top-level `pippit-tool-cli login` command.
|
|
31
|
+
func NewLoginCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
32
|
+
var forceRefresh bool
|
|
33
|
+
command := &cobra.Command{
|
|
34
|
+
Use: "login",
|
|
35
|
+
Short: "通过浏览器登录小云雀 CLI",
|
|
36
|
+
Args: cobra.NoArgs,
|
|
37
|
+
RunE: func(command *cobra.Command, _ []string) error {
|
|
38
|
+
manager, err := requireAuthManager(runner)
|
|
39
|
+
if err != nil {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
credential, err := manager.Login(command.Context(), internal_auth.LoginOptions{
|
|
43
|
+
Progress: stderr, ForceRefresh: forceRefresh,
|
|
44
|
+
})
|
|
45
|
+
if err != nil {
|
|
46
|
+
return err
|
|
47
|
+
}
|
|
48
|
+
if runner.Config != nil && strings.TrimSpace(runner.Config.AccessKey) != "" {
|
|
49
|
+
_, _ = fmt.Fprintln(stderr, "提示:当前进程设置了 XYQ_ACCESS_KEY,它会继续优先于刚保存的浏览器登录凭证。")
|
|
50
|
+
}
|
|
51
|
+
return writeJSON(stdout, loginResult{
|
|
52
|
+
LoggedIn: true,
|
|
53
|
+
Source: "browser",
|
|
54
|
+
UID: credential.UID,
|
|
55
|
+
CredentialScope: credential.CredentialScope,
|
|
56
|
+
ExpiresAt: time.Unix(credential.ExpiredAt, 0).Format(time.RFC3339),
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
command.SetOut(stdout)
|
|
61
|
+
command.SetErr(stderr)
|
|
62
|
+
command.Flags().BoolVar(&forceRefresh, "force", false, "强制轮换当前设备的 CLI Access Key(仅在旧密钥被拒绝时使用)")
|
|
63
|
+
return command
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// NewStatusCommand creates the top-level `pippit-tool-cli status` command.
|
|
67
|
+
func NewStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
68
|
+
command := &cobra.Command{
|
|
69
|
+
Use: "status",
|
|
70
|
+
Short: "查看小云雀 CLI 登录状态",
|
|
71
|
+
Args: cobra.NoArgs,
|
|
72
|
+
RunE: func(command *cobra.Command, _ []string) error {
|
|
73
|
+
manager, err := requireAuthManager(runner)
|
|
74
|
+
if err != nil {
|
|
75
|
+
return err
|
|
76
|
+
}
|
|
77
|
+
status, err := manager.Status(command.Context())
|
|
78
|
+
if err != nil {
|
|
79
|
+
return err
|
|
80
|
+
}
|
|
81
|
+
result := loginResult{LoggedIn: status.LoggedIn, Source: status.Source, UID: status.UID, CredentialScope: status.CredentialScope}
|
|
82
|
+
if !status.ExpiresAt.IsZero() {
|
|
83
|
+
result.ExpiresAt = status.ExpiresAt.Format(time.RFC3339)
|
|
84
|
+
}
|
|
85
|
+
return writeJSON(stdout, result)
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
command.SetOut(stdout)
|
|
89
|
+
command.SetErr(stderr)
|
|
90
|
+
return command
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// NewLogoutCommand clears only the browser-managed local credential. An
|
|
94
|
+
// explicit XYQ_ACCESS_KEY belongs to the caller's environment and is never
|
|
95
|
+
// modified or revoked by this command.
|
|
96
|
+
func NewLogoutCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
97
|
+
command := &cobra.Command{
|
|
98
|
+
Use: "logout",
|
|
99
|
+
Short: "清除本机小云雀 CLI 登录(不撤销远程 Access Key)",
|
|
100
|
+
Args: cobra.NoArgs,
|
|
101
|
+
RunE: func(command *cobra.Command, _ []string) error {
|
|
102
|
+
manager, err := requireAuthManager(runner)
|
|
103
|
+
if err != nil {
|
|
104
|
+
return err
|
|
105
|
+
}
|
|
106
|
+
if err := manager.Logout(command.Context(), false); err != nil && !errors.Is(err, internal_auth.ErrCredentialNotFound) {
|
|
107
|
+
return err
|
|
108
|
+
}
|
|
109
|
+
environmentActive := runner.Config != nil && strings.TrimSpace(runner.Config.AccessKey) != ""
|
|
110
|
+
if environmentActive {
|
|
111
|
+
_, _ = fmt.Fprintln(stderr, "提示:XYQ_ACCESS_KEY 仍由当前 shell 提供,CLI 无法替你清除该环境变量。")
|
|
112
|
+
}
|
|
113
|
+
_, _ = fmt.Fprintln(stderr, "已清除本机登录密钥;远程 Access Key 未撤销,并保留非秘密设备标识供下次登录安全复用。")
|
|
114
|
+
return writeJSON(stdout, logoutResult{
|
|
115
|
+
LoggedOut: true, EnvironmentStillActive: environmentActive, RemoteCredentialPreserved: true,
|
|
116
|
+
})
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
command.SetOut(stdout)
|
|
120
|
+
command.SetErr(stderr)
|
|
121
|
+
return command
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func requireAuthManager(runner *common.Runner) (common.AuthManager, error) {
|
|
125
|
+
if runner == nil || runner.Auth == nil {
|
|
126
|
+
return nil, fmt.Errorf("小云雀 CLI 浏览器授权尚未配置")
|
|
127
|
+
}
|
|
128
|
+
return runner.Auth, nil
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func writeJSON(writer io.Writer, value any) error {
|
|
132
|
+
payload, err := json.Marshal(value)
|
|
133
|
+
if err != nil {
|
|
134
|
+
return fmt.Errorf("编码登录结果失败: %w", err)
|
|
135
|
+
}
|
|
136
|
+
_, err = fmt.Fprintln(writer, string(payload))
|
|
137
|
+
return err
|
|
138
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package authcmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"io"
|
|
8
|
+
"strings"
|
|
9
|
+
"testing"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
internal_auth "github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
13
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
14
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
type fakeAuthManager struct {
|
|
18
|
+
credential *internal_auth.Credential
|
|
19
|
+
status *internal_auth.Status
|
|
20
|
+
loginProgress string
|
|
21
|
+
loginCalls int
|
|
22
|
+
loginOptions []internal_auth.LoginOptions
|
|
23
|
+
logoutCalls int
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (manager *fakeAuthManager) ResolveAccessKey(context.Context) (string, error) {
|
|
27
|
+
if manager.credential == nil {
|
|
28
|
+
return "", internal_auth.ErrCredentialNotFound
|
|
29
|
+
}
|
|
30
|
+
return manager.credential.AccessKey, nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func (manager *fakeAuthManager) Login(_ context.Context, options internal_auth.LoginOptions) (*internal_auth.Credential, error) {
|
|
34
|
+
manager.loginCalls++
|
|
35
|
+
manager.loginOptions = append(manager.loginOptions, options)
|
|
36
|
+
if options.Progress != nil {
|
|
37
|
+
message := manager.loginProgress
|
|
38
|
+
if message == "" {
|
|
39
|
+
message = "正在完成浏览器授权…\n"
|
|
40
|
+
}
|
|
41
|
+
_, _ = options.Progress.Write([]byte(message))
|
|
42
|
+
}
|
|
43
|
+
return manager.credential, nil
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func TestLoginCommandCanRequestSafeCredentialRotation(t *testing.T) {
|
|
47
|
+
manager := &fakeAuthManager{credential: &internal_auth.Credential{
|
|
48
|
+
UID: "123", CredentialScope: "account-device-scope", ExpiredAt: time.Now().Add(time.Hour).Unix(),
|
|
49
|
+
}}
|
|
50
|
+
command := NewLoginCommand(io.Discard, io.Discard, &common.Runner{Config: &config.Config{}, Auth: manager})
|
|
51
|
+
command.SetArgs([]string{"--force"})
|
|
52
|
+
if err := command.Execute(); err != nil {
|
|
53
|
+
t.Fatal(err)
|
|
54
|
+
}
|
|
55
|
+
if len(manager.loginOptions) != 1 || !manager.loginOptions[0].ForceRefresh {
|
|
56
|
+
t.Fatalf("login options = %#v, want forced refresh", manager.loginOptions)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func (manager *fakeAuthManager) Status(context.Context) (*internal_auth.Status, error) {
|
|
61
|
+
return manager.status, nil
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func (manager *fakeAuthManager) Logout(context.Context, bool) error {
|
|
65
|
+
manager.logoutCalls++
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func (manager *fakeAuthManager) CredentialScope(context.Context) (string, error) {
|
|
70
|
+
return "device-scope", nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestLoginCommandPrintsMetadataWithoutAccessKey(t *testing.T) {
|
|
74
|
+
const accessKey = "must-never-be-printed"
|
|
75
|
+
const loginURL = "https://xyq.jianying.com/cli/pippit-tool-login?callback=http%3A%2F%2F127.0.0.1%3A1234&random_secret_key=one-time-secret"
|
|
76
|
+
expiresAt := time.Now().Add(time.Hour).Unix()
|
|
77
|
+
manager := &fakeAuthManager{credential: &internal_auth.Credential{
|
|
78
|
+
AccessKey: accessKey, UID: "123", CredentialScope: "device-scope", ExpiredAt: expiresAt,
|
|
79
|
+
}, loginProgress: "正在完成浏览器授权…\n授权地址:\n" + loginURL + "\n"}
|
|
80
|
+
var stdout, stderr bytes.Buffer
|
|
81
|
+
command := NewLoginCommand(&stdout, &stderr, &common.Runner{Config: &config.Config{}, Auth: manager})
|
|
82
|
+
command.SetArgs(nil)
|
|
83
|
+
if err := command.Execute(); err != nil {
|
|
84
|
+
t.Fatalf("Execute() error = %v", err)
|
|
85
|
+
}
|
|
86
|
+
if manager.loginCalls != 1 || !strings.Contains(stderr.String(), "浏览器授权") {
|
|
87
|
+
t.Fatalf("login calls/stderr = %d/%q", manager.loginCalls, stderr.String())
|
|
88
|
+
}
|
|
89
|
+
if strings.Contains(stdout.String(), accessKey) || strings.Contains(stderr.String(), accessKey) {
|
|
90
|
+
t.Fatalf("command output leaked Access Key: stdout=%q stderr=%q", stdout.String(), stderr.String())
|
|
91
|
+
}
|
|
92
|
+
if !strings.Contains(stderr.String(), loginURL) || strings.Contains(stdout.String(), loginURL) {
|
|
93
|
+
t.Fatalf("login URL stream contract violated: stdout=%q stderr=%q", stdout.String(), stderr.String())
|
|
94
|
+
}
|
|
95
|
+
var result loginResult
|
|
96
|
+
if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
|
|
97
|
+
t.Fatal(err)
|
|
98
|
+
}
|
|
99
|
+
if !result.LoggedIn || result.Source != "browser" || result.UID != "123" || result.CredentialScope != "device-scope" {
|
|
100
|
+
t.Fatalf("login result = %#v", result)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func TestStatusAndLogoutDoNotModifyExplicitEnvironmentCredential(t *testing.T) {
|
|
105
|
+
manager := &fakeAuthManager{status: &internal_auth.Status{LoggedIn: true, Source: "environment"}}
|
|
106
|
+
runner := &common.Runner{Config: &config.Config{AccessKey: "explicit-ci-key"}, Auth: manager}
|
|
107
|
+
var statusOut, statusErr bytes.Buffer
|
|
108
|
+
status := NewStatusCommand(&statusOut, &statusErr, runner)
|
|
109
|
+
if err := status.Execute(); err != nil {
|
|
110
|
+
t.Fatal(err)
|
|
111
|
+
}
|
|
112
|
+
if strings.Contains(statusOut.String(), runner.Config.AccessKey) {
|
|
113
|
+
t.Fatal("status output leaked explicit Access Key")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var logoutOut, logoutErr bytes.Buffer
|
|
117
|
+
logout := NewLogoutCommand(&logoutOut, &logoutErr, runner)
|
|
118
|
+
if err := logout.Execute(); err != nil {
|
|
119
|
+
t.Fatal(err)
|
|
120
|
+
}
|
|
121
|
+
if manager.logoutCalls != 1 || runner.Config.AccessKey != "explicit-ci-key" {
|
|
122
|
+
t.Fatalf("logout calls/key = %d/%q", manager.logoutCalls, runner.Config.AccessKey)
|
|
123
|
+
}
|
|
124
|
+
if !strings.Contains(logoutErr.String(), "无法替你清除") || strings.Contains(logoutErr.String(), runner.Config.AccessKey) {
|
|
125
|
+
t.Fatalf("logout stderr = %q", logoutErr.String())
|
|
126
|
+
}
|
|
127
|
+
var result logoutResult
|
|
128
|
+
if err := json.Unmarshal(logoutOut.Bytes(), &result); err != nil {
|
|
129
|
+
t.Fatal(err)
|
|
130
|
+
}
|
|
131
|
+
if !result.LoggedOut || !result.RemoteCredentialPreserved || !strings.Contains(logoutErr.String(), "远程 Access Key 未撤销") {
|
|
132
|
+
t.Fatalf("logout result/stderr = %#v/%q", result, logoutErr.String())
|
|
133
|
+
}
|
|
134
|
+
}
|