bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
173 lines
5.5 KiB
Go
173 lines
5.5 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package drive
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
const (
|
|
driveDeleteVisibilityTimeout = 30 * time.Second
|
|
driveDeleteVisibilityPoll = 3 * time.Second
|
|
)
|
|
|
|
var driveDeleteVisibilityWait = clie2e.WaitOptions{
|
|
// This wait only covers the post-delete visibility lag after Drive accepts
|
|
// deletion. The delete command itself is bounded by clie2e.CleanupContext.
|
|
Timeout: driveDeleteVisibilityTimeout,
|
|
Interval: driveDeleteVisibilityPoll,
|
|
}
|
|
|
|
// CreateDriveFolder creates a Drive folder, optionally under a parent folder, and
|
|
// deletes it during parent cleanup.
|
|
func CreateDriveFolder(t *testing.T, parentT *testing.T, ctx context.Context, name string, defaultAs string, parentFolderToken string) string {
|
|
t.Helper()
|
|
|
|
if defaultAs == "" {
|
|
defaultAs = "bot"
|
|
}
|
|
|
|
args := []string{"drive", "+create-folder", "--name", name}
|
|
if parentFolderToken != "" {
|
|
args = append(args, "--folder-token", parentFolderToken)
|
|
}
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: args,
|
|
DefaultAs: defaultAs,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create drive folder %q: %v", name, err)
|
|
}
|
|
result.AssertExitCode(t, 0)
|
|
result.AssertStdoutStatus(t, true)
|
|
|
|
folderToken := gjson.Get(result.Stdout, "data.folder_token").String()
|
|
if folderToken == "" {
|
|
t.Fatalf("drive folder token should not be empty, stdout:\n%s", result.Stdout)
|
|
}
|
|
|
|
parentT.Cleanup(func() {
|
|
cleanupCtx, cancel := clie2e.CleanupContext()
|
|
defer cancel()
|
|
|
|
deleteResult, deleteErr := DeleteDriveResourceAndVerify(cleanupCtx, folderToken, "folder", defaultAs)
|
|
clie2e.ReportCleanupFailure(parentT, "delete drive folder "+folderToken, deleteResult, deleteErr)
|
|
})
|
|
|
|
return folderToken
|
|
}
|
|
|
|
// DeleteDriveResourceAndVerify deletes a Drive-backed resource, then polls
|
|
// drive meta until the token is either gone or no longer has an accessible URL.
|
|
// This prevents cleanup from looking successful when the delete command
|
|
// returned a suppressed not_found or partial API error but the resource still
|
|
// exists.
|
|
func DeleteDriveResourceAndVerify(ctx context.Context, token, docType, defaultAs string) (*clie2e.Result, error) {
|
|
return deleteDriveResourceAndVerify(ctx, token, docType, defaultAs, driveDeleteVisibilityWait)
|
|
}
|
|
|
|
func deleteDriveResourceAndVerify(ctx context.Context, token, docType, defaultAs string, visibilityWait clie2e.WaitOptions) (*clie2e.Result, error) {
|
|
if defaultAs == "" {
|
|
defaultAs = "bot"
|
|
}
|
|
|
|
deleteResult, deleteErr := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{"drive", "+delete", "--file-token", token, "--type", docType, "--yes"},
|
|
DefaultAs: defaultAs,
|
|
})
|
|
if deleteErr != nil || deleteResult == nil {
|
|
return deleteResult, deleteErr
|
|
}
|
|
if deleteResult.ExitCode != 0 {
|
|
deleted, verifyErr := IsDriveResourceDeleted(ctx, token, docType, defaultAs)
|
|
if verifyErr != nil {
|
|
return deleteResult, verifyErr
|
|
}
|
|
if deleted {
|
|
deleteResult.ExitCode = 0
|
|
return deleteResult, nil
|
|
}
|
|
return deleteResult, fmt.Errorf("drive resource %s/%s still exists after delete failed: exit=%d stdout=%s stderr=%s", docType, token, deleteResult.ExitCode, deleteResult.Stdout, deleteResult.Stderr)
|
|
}
|
|
if err := waitDriveResourceDeleted(ctx, token, docType, defaultAs, visibilityWait); err != nil {
|
|
return deleteResult, clie2e.CleanupWarning(
|
|
fmt.Errorf("drive resource %s/%s still visible after accepted delete: %w", docType, token, err),
|
|
)
|
|
}
|
|
return deleteResult, nil
|
|
}
|
|
|
|
func waitDriveResourceDeleted(ctx context.Context, token, docType, defaultAs string, opts clie2e.WaitOptions) error {
|
|
opts.TimeoutError = func() error {
|
|
return fmt.Errorf("drive resource %s/%s still exists %s after delete", docType, token, opts.Timeout)
|
|
}
|
|
return clie2e.WaitForCondition(ctx, opts, func() (bool, error) {
|
|
return IsDriveResourceDeleted(ctx, token, docType, defaultAs)
|
|
})
|
|
}
|
|
|
|
func IsDriveResourceDeleted(ctx context.Context, token, docType, defaultAs string) (bool, error) {
|
|
if defaultAs == "" {
|
|
defaultAs = "bot"
|
|
}
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{"api", "post", "/open-apis/drive/v1/metas/batch_query"},
|
|
DefaultAs: defaultAs,
|
|
Data: map[string]any{
|
|
"request_docs": []map[string]any{{
|
|
"doc_token": token,
|
|
"doc_type": docType,
|
|
}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if result.ExitCode != 0 {
|
|
combined := strings.ToLower(result.Stdout + "\n" + result.Stderr)
|
|
if strings.Contains(combined, "not found") || strings.Contains(combined, "404") {
|
|
return true, nil
|
|
}
|
|
return false, fmt.Errorf("verify drive resource %s/%s after delete: exit=%d stdout=%s stderr=%s", docType, token, result.ExitCode, result.Stdout, result.Stderr)
|
|
}
|
|
if !isDriveMetaQuerySuccessful(result.Stdout) {
|
|
return false, fmt.Errorf("verify drive resource %s/%s after delete returned unsuccessful envelope: stdout=%s stderr=%s", docType, token, result.Stdout, result.Stderr)
|
|
}
|
|
metas := gjson.Get(result.Stdout, "data.metas").Array()
|
|
if len(metas) == 0 {
|
|
return true, nil
|
|
}
|
|
if docType != "folder" {
|
|
allURLsCleared := true
|
|
for _, meta := range metas {
|
|
if meta.Get("url").String() != "" {
|
|
allURLsCleared = false
|
|
break
|
|
}
|
|
}
|
|
if allURLsCleared {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func isDriveMetaQuerySuccessful(stdout string) bool {
|
|
if ok := gjson.Get(stdout, "ok"); ok.Exists() {
|
|
return ok.Bool()
|
|
}
|
|
if code := gjson.Get(stdout, "code"); code.Exists() {
|
|
return code.Int() == 0
|
|
}
|
|
return false
|
|
}
|