@pippit-dev/cli 1.0.18 → 1.0.20
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 +18 -0
- package/checksums.txt +6 -6
- package/cmd/canvas/canvas.go +8 -2
- package/cmd/canvas/canvas_test.go +56 -0
- package/cmd/get_credit_balance.go +37 -0
- package/cmd/get_credit_balance_test.go +73 -0
- package/cmd/root.go +2 -1
- package/cmd/root_test.go +9 -0
- package/cmd/short_drama_test.go +1 -0
- package/dist/checksums.txt +6 -0
- package/dist/xyq-canvas-command-runtime.cjs +16 -0
- package/dist/xyq-canvas-command-runtime.cjs.LEGAL.txt +599 -0
- package/dist/xyq-canvas-command-runtime.cjs.sha256 +2 -0
- package/internal/canvas/apply.go +104 -28
- package/internal/canvas/canvas_test.go +113 -0
- package/internal/common/get_credit_balance.go +61 -0
- package/internal/common/get_credit_balance_test.go +75 -0
- package/internal/config/config.go +11 -8
- package/internal/config/config_test.go +3 -0
- package/package.json +9 -2
- package/scripts/canvas-command.js +881 -0
- package/scripts/run.js +21 -4
package/internal/canvas/apply.go
CHANGED
|
@@ -13,8 +13,9 @@ import (
|
|
|
13
13
|
var decimalIDPattern = regexp.MustCompile(`^[1-9][0-9]*$`)
|
|
14
14
|
|
|
15
15
|
type ApplyOptions struct {
|
|
16
|
-
ProjectID
|
|
17
|
-
Request
|
|
16
|
+
ProjectID string
|
|
17
|
+
Request ApplyRequest
|
|
18
|
+
AllowNonAcknowledgedResults bool
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
type ApplyRequest struct {
|
|
@@ -47,6 +48,8 @@ type ApplyResult struct {
|
|
|
47
48
|
BatchID string `json:"batch_id"`
|
|
48
49
|
Results []PatchTransactionResult `json:"results"`
|
|
49
50
|
LogID string `json:"log_id,omitempty"`
|
|
51
|
+
|
|
52
|
+
rawResults []json.RawMessage
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
type PatchTransactionResult struct {
|
|
@@ -58,7 +61,29 @@ type PatchTransactionResult struct {
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
type applyData struct {
|
|
61
|
-
Results []
|
|
64
|
+
Results []json.RawMessage `json:"results"`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type decodedPatchTransactionResult struct {
|
|
68
|
+
result PatchTransactionResult
|
|
69
|
+
raw json.RawMessage
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func (result ApplyResult) MarshalJSON() ([]byte, error) {
|
|
73
|
+
type applyResultJSON struct {
|
|
74
|
+
BatchID string `json:"batch_id"`
|
|
75
|
+
Results any `json:"results"`
|
|
76
|
+
LogID string `json:"log_id,omitempty"`
|
|
77
|
+
}
|
|
78
|
+
results := any(result.Results)
|
|
79
|
+
if len(result.rawResults) > 0 {
|
|
80
|
+
results = result.rawResults
|
|
81
|
+
}
|
|
82
|
+
return json.Marshal(applyResultJSON{
|
|
83
|
+
BatchID: result.BatchID,
|
|
84
|
+
Results: results,
|
|
85
|
+
LogID: result.LogID,
|
|
86
|
+
})
|
|
62
87
|
}
|
|
63
88
|
|
|
64
89
|
func Apply(ctx context.Context, opts ApplyOptions, runner *common.Runner) (*ApplyResult, error) {
|
|
@@ -90,18 +115,33 @@ func Apply(ctx context.Context, opts ApplyOptions, runner *common.Runner) (*Appl
|
|
|
90
115
|
envelope.LogID,
|
|
91
116
|
)
|
|
92
117
|
}
|
|
93
|
-
|
|
118
|
+
decodedResults, err := decodeApplyResults(data.Results)
|
|
119
|
+
if err != nil {
|
|
120
|
+
return nil, common.NewLogIDError(
|
|
121
|
+
fmt.Sprintf("canvas apply returned invalid data: %v; query affected assets before retrying because outcome cannot be confirmed", err),
|
|
122
|
+
envelope.LogID,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
ordered, rawResults, _, err := validateApplyResults(
|
|
126
|
+
request.Transactions,
|
|
127
|
+
decodedResults,
|
|
128
|
+
opts.AllowNonAcknowledgedResults,
|
|
129
|
+
)
|
|
94
130
|
if err != nil {
|
|
95
131
|
return nil, common.NewLogIDError(
|
|
96
132
|
fmt.Sprintf("%s; query affected assets before retrying because outcome cannot be confirmed", err),
|
|
97
133
|
envelope.LogID,
|
|
98
134
|
)
|
|
99
135
|
}
|
|
100
|
-
|
|
136
|
+
result := &ApplyResult{
|
|
101
137
|
BatchID: request.BatchID,
|
|
102
138
|
Results: ordered,
|
|
103
139
|
LogID: strings.TrimSpace(envelope.LogID),
|
|
104
|
-
}
|
|
140
|
+
}
|
|
141
|
+
if opts.AllowNonAcknowledgedResults {
|
|
142
|
+
result.rawResults = rawResults
|
|
143
|
+
}
|
|
144
|
+
return result, nil
|
|
105
145
|
}
|
|
106
146
|
|
|
107
147
|
func validateApplyRequest(request *ApplyRequest) error {
|
|
@@ -161,43 +201,79 @@ func validateApplyRequest(request *ApplyRequest) error {
|
|
|
161
201
|
return nil
|
|
162
202
|
}
|
|
163
203
|
|
|
164
|
-
func
|
|
165
|
-
|
|
166
|
-
for
|
|
204
|
+
func decodeApplyResults(rawResults []json.RawMessage) ([]decodedPatchTransactionResult, error) {
|
|
205
|
+
results := make([]decodedPatchTransactionResult, 0, len(rawResults))
|
|
206
|
+
for index, rawResult := range rawResults {
|
|
207
|
+
var result PatchTransactionResult
|
|
208
|
+
if err := json.Unmarshal(rawResult, &result); err != nil {
|
|
209
|
+
return nil, fmt.Errorf("decode transaction result[%d]: %w", index, err)
|
|
210
|
+
}
|
|
211
|
+
results = append(results, decodedPatchTransactionResult{result: result, raw: rawResult})
|
|
212
|
+
}
|
|
213
|
+
return results, nil
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func validateApplyResults(
|
|
217
|
+
expected []PatchTransaction,
|
|
218
|
+
actual []decodedPatchTransactionResult,
|
|
219
|
+
allowNonAcknowledgedResults bool,
|
|
220
|
+
) ([]PatchTransactionResult, []json.RawMessage, bool, error) {
|
|
221
|
+
byID := make(map[string]decodedPatchTransactionResult, len(actual))
|
|
222
|
+
for _, decoded := range actual {
|
|
223
|
+
result := decoded.result
|
|
167
224
|
result.TransactionID = strings.TrimSpace(result.TransactionID)
|
|
168
225
|
if result.TransactionID == "" {
|
|
169
|
-
return nil, fmt.Errorf("canvas apply returned a result without transaction_id")
|
|
226
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned a result without transaction_id")
|
|
170
227
|
}
|
|
171
228
|
if _, duplicate := byID[result.TransactionID]; duplicate {
|
|
172
|
-
return nil, fmt.Errorf("canvas apply returned duplicate result for transaction %q", result.TransactionID)
|
|
229
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned duplicate result for transaction %q", result.TransactionID)
|
|
173
230
|
}
|
|
174
|
-
|
|
231
|
+
decoded.result = result
|
|
232
|
+
byID[result.TransactionID] = decoded
|
|
175
233
|
}
|
|
176
234
|
if len(byID) != len(expected) {
|
|
177
|
-
return nil, fmt.Errorf("canvas apply returned %d transaction results, want %d", len(byID), len(expected))
|
|
235
|
+
return nil, nil, false, fmt.Errorf("canvas apply returned %d transaction results, want %d", len(byID), len(expected))
|
|
178
236
|
}
|
|
179
237
|
ordered := make([]PatchTransactionResult, 0, len(expected))
|
|
238
|
+
orderedRaw := make([]json.RawMessage, 0, len(expected))
|
|
239
|
+
hasNonAcknowledgedResult := false
|
|
180
240
|
for _, transaction := range expected {
|
|
181
|
-
|
|
241
|
+
decoded, ok := byID[transaction.TransactionID]
|
|
182
242
|
if !ok {
|
|
183
|
-
return nil, fmt.Errorf("canvas apply omitted transaction result %q", transaction.TransactionID)
|
|
184
|
-
}
|
|
185
|
-
if strings.ToLower(strings.TrimSpace(result.Status)) != "ack" {
|
|
186
|
-
return nil, fmt.Errorf(
|
|
187
|
-
"canvas apply transaction %q was not acknowledged: status=%q blocked_by=%q error=%q",
|
|
188
|
-
transaction.TransactionID, result.Status, result.BlockedByTransaction, result.Error,
|
|
189
|
-
)
|
|
243
|
+
return nil, nil, false, fmt.Errorf("canvas apply omitted transaction result %q", transaction.TransactionID)
|
|
190
244
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
245
|
+
result := decoded.result
|
|
246
|
+
status := strings.ToLower(strings.TrimSpace(result.Status))
|
|
247
|
+
switch status {
|
|
248
|
+
case "ack":
|
|
249
|
+
for _, patch := range transaction.Patches {
|
|
250
|
+
version, ok := result.AssetVersions[patch.AssetID]
|
|
251
|
+
if !ok {
|
|
252
|
+
if allowNonAcknowledgedResults && patch.Op == "add" && patch.Path == "" {
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
return nil, nil, false, fmt.Errorf("canvas apply transaction %q omitted version for asset %q", transaction.TransactionID, patch.AssetID)
|
|
256
|
+
}
|
|
257
|
+
if version < 0 {
|
|
258
|
+
return nil, nil, false, fmt.Errorf("canvas apply transaction %q returned negative version for asset %q", transaction.TransactionID, patch.AssetID)
|
|
259
|
+
}
|
|
195
260
|
}
|
|
196
|
-
|
|
197
|
-
|
|
261
|
+
case "reject", "blocked", "skipped":
|
|
262
|
+
if !allowNonAcknowledgedResults {
|
|
263
|
+
return nil, nil, false, fmt.Errorf(
|
|
264
|
+
"canvas apply transaction %q was not acknowledged: status=%q blocked_by=%q error=%q",
|
|
265
|
+
transaction.TransactionID, result.Status, result.BlockedByTransaction, result.Error,
|
|
266
|
+
)
|
|
198
267
|
}
|
|
268
|
+
hasNonAcknowledgedResult = true
|
|
269
|
+
default:
|
|
270
|
+
return nil, nil, false, fmt.Errorf(
|
|
271
|
+
"canvas apply transaction %q returned invalid status %q",
|
|
272
|
+
transaction.TransactionID, result.Status,
|
|
273
|
+
)
|
|
199
274
|
}
|
|
200
275
|
ordered = append(ordered, result)
|
|
276
|
+
orderedRaw = append(orderedRaw, decoded.raw)
|
|
201
277
|
}
|
|
202
|
-
return ordered, nil
|
|
278
|
+
return ordered, orderedRaw, hasNonAcknowledgedResult, nil
|
|
203
279
|
}
|
|
@@ -336,6 +336,119 @@ func TestApplyRequiresAcknowledgementAndEveryAssetVersion(t *testing.T) {
|
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
func TestApplyAllowsExplicitTransportOutcomesWithoutHidingServerResultFields(t *testing.T) {
|
|
340
|
+
request := ApplyRequest{
|
|
341
|
+
BatchID: "batch-1", ClientID: "client-1",
|
|
342
|
+
Transactions: []PatchTransaction{{TransactionID: "tx-1", Patches: []PatchEntry{{
|
|
343
|
+
AssetID: "asset-1", Op: "replace", Path: "", Value: json.RawMessage(`{}`),
|
|
344
|
+
}}}},
|
|
345
|
+
}
|
|
346
|
+
for _, status := range []string{"reject", "blocked", "skipped"} {
|
|
347
|
+
t.Run(status, func(t *testing.T) {
|
|
348
|
+
client := &fakeClient{send: func(_ context.Context, _ string, _ any, out any) error {
|
|
349
|
+
response := fmt.Sprintf(
|
|
350
|
+
`{"ret":"0","log_id":"transport-log","data":{"results":[{"transaction_id":"tx-1","status":%q,"blocked_by":"tx-0","message":"server detail","current_asset_versions":{"asset-1":7},"server_detail":{"reason":"conflict"}}]}}`,
|
|
351
|
+
status,
|
|
352
|
+
)
|
|
353
|
+
return decodeInto(out, response)
|
|
354
|
+
}}
|
|
355
|
+
result, err := Apply(context.Background(), ApplyOptions{
|
|
356
|
+
Request: request,
|
|
357
|
+
AllowNonAcknowledgedResults: true,
|
|
358
|
+
}, runnerWithClient(client))
|
|
359
|
+
if err != nil || result.Results[0].Status != status {
|
|
360
|
+
t.Fatalf("Apply() = (%#v, %v), want structured %s result", result, err, status)
|
|
361
|
+
}
|
|
362
|
+
payload, err := json.Marshal(result)
|
|
363
|
+
if err != nil {
|
|
364
|
+
t.Fatalf("Marshal() error = %v", err)
|
|
365
|
+
}
|
|
366
|
+
var output map[string]any
|
|
367
|
+
if err := json.Unmarshal(payload, &output); err != nil {
|
|
368
|
+
t.Fatalf("Unmarshal() error = %v", err)
|
|
369
|
+
}
|
|
370
|
+
entry := output["results"].([]any)[0].(map[string]any)
|
|
371
|
+
if entry["message"] != "server detail" || entry["server_detail"] == nil || entry["current_asset_versions"] == nil {
|
|
372
|
+
t.Fatalf("result = %#v, want complete server transaction result", entry)
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
func TestApplyTransportResultAllowsRootAssetCreateAckWithoutVersion(t *testing.T) {
|
|
379
|
+
request := ApplyRequest{
|
|
380
|
+
BatchID: "batch-1", ClientID: "client-1",
|
|
381
|
+
Transactions: []PatchTransaction{{TransactionID: "tx-1", Patches: []PatchEntry{{
|
|
382
|
+
AssetID: "asset-new", Op: "add", Path: "", Value: json.RawMessage(`{"nodes":[]}`),
|
|
383
|
+
}}}},
|
|
384
|
+
}
|
|
385
|
+
client := &fakeClient{send: func(_ context.Context, _ string, _ any, out any) error {
|
|
386
|
+
return decodeInto(out, `{"ret":"0","data":{"results":[{"transaction_id":"tx-1","status":"ack","server_detail":{"created":true}}]}}`)
|
|
387
|
+
}}
|
|
388
|
+
result, err := Apply(context.Background(), ApplyOptions{
|
|
389
|
+
Request: request,
|
|
390
|
+
AllowNonAcknowledgedResults: true,
|
|
391
|
+
}, runnerWithClient(client))
|
|
392
|
+
if err != nil || result.Results[0].Status != "ack" {
|
|
393
|
+
t.Fatalf("Apply() = (%#v, %v), want transport ACK", result, err)
|
|
394
|
+
}
|
|
395
|
+
payload, err := json.Marshal(result)
|
|
396
|
+
if err != nil {
|
|
397
|
+
t.Fatalf("Marshal() error = %v", err)
|
|
398
|
+
}
|
|
399
|
+
var output map[string]any
|
|
400
|
+
if err := json.Unmarshal(payload, &output); err != nil {
|
|
401
|
+
t.Fatalf("Unmarshal() error = %v", err)
|
|
402
|
+
}
|
|
403
|
+
entry := output["results"].([]any)[0].(map[string]any)
|
|
404
|
+
if entry["asset_versions"] != nil || entry["server_detail"] == nil {
|
|
405
|
+
t.Fatalf("result = %#v, want unchanged ACK for SDK decoder", entry)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
func TestApplyTransportResultStillRequiresVersionsForNonCreatePatches(t *testing.T) {
|
|
410
|
+
for _, patch := range []PatchEntry{
|
|
411
|
+
{AssetID: "asset-1", Op: "replace", Path: "", Value: json.RawMessage(`{}`)},
|
|
412
|
+
{AssetID: "asset-1", Op: "add", Path: "/nodes/0", Value: json.RawMessage(`{}`)},
|
|
413
|
+
} {
|
|
414
|
+
t.Run(patch.Op+patch.Path, func(t *testing.T) {
|
|
415
|
+
request := ApplyRequest{
|
|
416
|
+
BatchID: "batch-1", ClientID: "client-1",
|
|
417
|
+
Transactions: []PatchTransaction{{TransactionID: "tx-1", Patches: []PatchEntry{patch}}},
|
|
418
|
+
}
|
|
419
|
+
client := &fakeClient{send: func(_ context.Context, _ string, _ any, out any) error {
|
|
420
|
+
return decodeInto(out, `{"ret":"0","data":{"results":[{"transaction_id":"tx-1","status":"ack"}]}}`)
|
|
421
|
+
}}
|
|
422
|
+
_, err := Apply(context.Background(), ApplyOptions{
|
|
423
|
+
Request: request,
|
|
424
|
+
AllowNonAcknowledgedResults: true,
|
|
425
|
+
}, runnerWithClient(client))
|
|
426
|
+
if err == nil || !strings.Contains(err.Error(), "omitted version") {
|
|
427
|
+
t.Fatalf("Apply() error = %v, want missing version rejection", err)
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
func TestApplyTransportResultModeStillRejectsInvalidStatus(t *testing.T) {
|
|
434
|
+
request := ApplyRequest{
|
|
435
|
+
BatchID: "batch-1", ClientID: "client-1",
|
|
436
|
+
Transactions: []PatchTransaction{{TransactionID: "tx-1", Patches: []PatchEntry{{
|
|
437
|
+
AssetID: "asset-1", Op: "replace", Path: "", Value: json.RawMessage(`{}`),
|
|
438
|
+
}}}},
|
|
439
|
+
}
|
|
440
|
+
client := &fakeClient{send: func(_ context.Context, _ string, _ any, out any) error {
|
|
441
|
+
return decodeInto(out, `{"ret":"0","data":{"results":[{"transaction_id":"tx-1","status":"pending"}]}}`)
|
|
442
|
+
}}
|
|
443
|
+
_, err := Apply(context.Background(), ApplyOptions{
|
|
444
|
+
Request: request,
|
|
445
|
+
AllowNonAcknowledgedResults: true,
|
|
446
|
+
}, runnerWithClient(client))
|
|
447
|
+
if err == nil || !strings.Contains(err.Error(), "invalid status") {
|
|
448
|
+
t.Fatalf("Apply() error = %v, want invalid response rejection", err)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
339
452
|
func TestApplyBetaRejectsMultipleTransactionsBeforeRequest(t *testing.T) {
|
|
340
453
|
requestCalls := 0
|
|
341
454
|
client := &fakeClient{send: func(context.Context, string, any, any) error {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// GetCreditBalanceResult is the JSON output printed by `pippit-tool-cli get-credit-balance`.
|
|
11
|
+
type GetCreditBalanceResult struct {
|
|
12
|
+
TotalRemainAmount int64 `json:"total_remain_amount,string"`
|
|
13
|
+
LogID string `json:"-"`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type getCreditBalanceResponse struct {
|
|
17
|
+
Ret string `json:"ret"`
|
|
18
|
+
Errmsg string `json:"errmsg"`
|
|
19
|
+
LogID string `json:"log_id"`
|
|
20
|
+
Data *getCreditBalanceResponseData `json:"data"`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type getCreditBalanceResponseData struct {
|
|
24
|
+
TotalRemainAmount *int64 `json:"total_remain_amount,string"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// GetCreditBalance queries the effective credit balance of the access-key owner.
|
|
28
|
+
func GetCreditBalance(ctx context.Context, runner *Runner) (*GetCreditBalanceResult, error) {
|
|
29
|
+
if runner == nil || runner.Client == nil {
|
|
30
|
+
return nil, fmt.Errorf("get_credit_balance 运行器客户端缺失")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var resp getCreditBalanceResponse
|
|
34
|
+
if err := runner.Client.SendRequest(ctx, getCreditBalancePath(runner), struct{}{}, &resp); err != nil {
|
|
35
|
+
return nil, fmt.Errorf("获取积分余额请求失败: %w", err)
|
|
36
|
+
}
|
|
37
|
+
if resp.Ret != "0" {
|
|
38
|
+
if resp.Errmsg == "" {
|
|
39
|
+
resp.Errmsg = "未知错误"
|
|
40
|
+
}
|
|
41
|
+
return nil, NewLogIDError(fmt.Sprintf("获取积分余额请求返回失败: ret=%s errmsg=%s", resp.Ret, resp.Errmsg), resp.LogID)
|
|
42
|
+
}
|
|
43
|
+
if resp.Data == nil {
|
|
44
|
+
return nil, NewLogIDError("get_credit_balance 响应缺少 data", resp.LogID)
|
|
45
|
+
}
|
|
46
|
+
if resp.Data.TotalRemainAmount == nil {
|
|
47
|
+
return nil, NewLogIDError("get_credit_balance 响应缺少 data.total_remain_amount", resp.LogID)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return &GetCreditBalanceResult{
|
|
51
|
+
TotalRemainAmount: *resp.Data.TotalRemainAmount,
|
|
52
|
+
LogID: resp.LogID,
|
|
53
|
+
}, nil
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func getCreditBalancePath(runner *Runner) string {
|
|
57
|
+
if runner != nil && runner.Config != nil && runner.Config.Paths != nil && runner.Config.Paths.GetCreditBalance != "" {
|
|
58
|
+
return runner.Config.Paths.GetCreditBalance
|
|
59
|
+
}
|
|
60
|
+
return config.GetCreditBalancePath
|
|
61
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/bytedance/sonic"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
type creditBalanceFakeClient struct {
|
|
13
|
+
response string
|
|
14
|
+
path string
|
|
15
|
+
body any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func (c *creditBalanceFakeClient) SendRequest(_ context.Context, path string, body any, out any) error {
|
|
19
|
+
c.path = path
|
|
20
|
+
c.body = body
|
|
21
|
+
return sonic.Unmarshal([]byte(c.response), out)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (c *creditBalanceFakeClient) SendRequestWithHeaders(ctx context.Context, path string, body any, _ map[string]string, out any) error {
|
|
25
|
+
return c.SendRequest(ctx, path, body, out)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (c *creditBalanceFakeClient) SendMultipartRequest(context.Context, string, map[string]string, MultipartFile, any) error {
|
|
29
|
+
return fmt.Errorf("unexpected multipart request")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func TestGetCreditBalanceReturnsAmount(t *testing.T) {
|
|
33
|
+
client := &creditBalanceFakeClient{
|
|
34
|
+
response: `{"ret":"0","log_id":"log_balance","data":{"total_remain_amount":"4321"}}`,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
result, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
38
|
+
if err != nil {
|
|
39
|
+
t.Fatalf("GetCreditBalance() error = %v", err)
|
|
40
|
+
}
|
|
41
|
+
if result.TotalRemainAmount != 4321 {
|
|
42
|
+
t.Fatalf("TotalRemainAmount = %d, want 4321", result.TotalRemainAmount)
|
|
43
|
+
}
|
|
44
|
+
if result.LogID != "log_balance" {
|
|
45
|
+
t.Fatalf("LogID = %q, want log_balance", result.LogID)
|
|
46
|
+
}
|
|
47
|
+
if client.path != "/api/biz/v1/skill/get_credit_balance" {
|
|
48
|
+
t.Fatalf("path = %q, want get_credit_balance path", client.path)
|
|
49
|
+
}
|
|
50
|
+
if _, ok := client.body.(struct{}); !ok {
|
|
51
|
+
t.Fatalf("body = %#v, want an empty POST body", client.body)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func TestGetCreditBalanceRequiresOptionalAmount(t *testing.T) {
|
|
56
|
+
client := &creditBalanceFakeClient{
|
|
57
|
+
response: `{"ret":"0","log_id":"log_missing","data":{}}`,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
61
|
+
if err == nil || !strings.Contains(err.Error(), "data.total_remain_amount") || !strings.Contains(err.Error(), "log_id=log_missing") {
|
|
62
|
+
t.Fatalf("GetCreditBalance() error = %v, want missing amount and log_id", err)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func TestGetCreditBalanceBusinessErrorIncludesLogID(t *testing.T) {
|
|
67
|
+
client := &creditBalanceFakeClient{
|
|
68
|
+
response: `{"ret":"16008","errmsg":"查询失败","log_id":"log_123"}`,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_, err := GetCreditBalance(context.Background(), &Runner{Client: client})
|
|
72
|
+
if err == nil || !strings.Contains(err.Error(), "ret=16008 errmsg=查询失败") || !strings.Contains(err.Error(), "log_id=log_123") {
|
|
73
|
+
t.Fatalf("GetCreditBalance() error = %v, want business error and log_id", err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -12,6 +12,7 @@ const (
|
|
|
12
12
|
DefaultAuthTTL = 30 * time.Second
|
|
13
13
|
DefaultAuthStoreServiceName = "pippit-cli"
|
|
14
14
|
SubmitRunPath = "/api/biz/v1/skill/submit_run"
|
|
15
|
+
GetCreditBalancePath = "/api/biz/v1/skill/get_credit_balance"
|
|
15
16
|
GetThreadPath = "/api/biz/v1/skill/get_thread"
|
|
16
17
|
UploadFilePath = "/api/biz/v1/skill/upload_file"
|
|
17
18
|
ListThreadFilePath = "/api/biz/v1/skill/list_thread_file"
|
|
@@ -29,10 +30,11 @@ type Config struct {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
type Paths struct {
|
|
32
|
-
SubmitRun
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
SubmitRun string
|
|
34
|
+
GetCreditBalance string
|
|
35
|
+
GetThread string
|
|
36
|
+
UploadFile string
|
|
37
|
+
ListThreadFile string
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
// Load resolves the built-in runtime config.
|
|
@@ -43,10 +45,11 @@ func Load() *Config {
|
|
|
43
45
|
AuthTTL: DefaultAuthTTL,
|
|
44
46
|
AccessKey: strings.TrimSpace(os.Getenv(EnvXYQAccessKey)),
|
|
45
47
|
Paths: &Paths{
|
|
46
|
-
SubmitRun:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
SubmitRun: SubmitRunPath,
|
|
49
|
+
GetCreditBalance: GetCreditBalancePath,
|
|
50
|
+
GetThread: GetThreadPath,
|
|
51
|
+
UploadFile: UploadFilePath,
|
|
52
|
+
ListThreadFile: ListThreadFilePath,
|
|
50
53
|
},
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -20,6 +20,9 @@ func TestLoadUsesDefaultConfig(t *testing.T) {
|
|
|
20
20
|
if cfg.Paths.SubmitRun != SubmitRunPath {
|
|
21
21
|
t.Fatalf("SubmitRun path = %q, want %q", cfg.Paths.SubmitRun, SubmitRunPath)
|
|
22
22
|
}
|
|
23
|
+
if cfg.Paths.GetCreditBalance != GetCreditBalancePath {
|
|
24
|
+
t.Fatalf("GetCreditBalance path = %q, want %q", cfg.Paths.GetCreditBalance, GetCreditBalancePath)
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
func TestLoadReadsAccessKey(t *testing.T) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pippit-dev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Pippit CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pippit-tool-cli": "scripts/run.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"postinstall": "node scripts/install.js",
|
|
10
|
-
"
|
|
10
|
+
"prepack": "node scripts/prepare-canvas-runtime.js",
|
|
11
|
+
"prepare:canvas-runtime": "node scripts/prepare-canvas-runtime.js",
|
|
12
|
+
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/install-wizard.test.js && node scripts/canvas-command.test.js && node scripts/prepare-canvas-runtime.test.js && go test ./... && go vet ./...",
|
|
13
|
+
"verify:canvas-runtime": "node scripts/prepare-canvas-runtime.js --check-only"
|
|
11
14
|
},
|
|
12
15
|
"os": [
|
|
13
16
|
"darwin",
|
|
@@ -32,11 +35,15 @@
|
|
|
32
35
|
"skills",
|
|
33
36
|
"scripts/install.js",
|
|
34
37
|
"scripts/install-wizard.js",
|
|
38
|
+
"scripts/canvas-command.js",
|
|
35
39
|
"scripts/platform.js",
|
|
36
40
|
"scripts/run.js",
|
|
37
41
|
"scripts/skills.js",
|
|
38
42
|
"scripts/telemetry.js",
|
|
39
43
|
"scripts/version-check.js",
|
|
44
|
+
"dist/xyq-canvas-command-runtime.cjs",
|
|
45
|
+
"dist/xyq-canvas-command-runtime.cjs.LEGAL.txt",
|
|
46
|
+
"dist/xyq-canvas-command-runtime.cjs.sha256",
|
|
40
47
|
"checksums.txt",
|
|
41
48
|
"README.md",
|
|
42
49
|
"LICENSE"
|