mirror of
https://github.com/larksuite/cli.git
synced 2026-07-10 02:54:04 +08:00
Compare commits
1 Commits
main
...
fix/unify-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f437118473 |
@@ -271,6 +271,9 @@ func resolveBaseURL(u *url.URL) map[string]interface{} {
|
||||
|
||||
func resolveWikiBaseURL(runtime *common.RuntimeContext, u *url.URL) (map[string]interface{}, error) {
|
||||
token := firstPathSegmentAfter(u.Path, "/wiki/")
|
||||
if err := runtime.EnsureScopes([]string{"wiki:node:retrieve"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := runtime.CallAPITyped("GET", "/open-apis/wiki/v2/spaces/get_node", map[string]interface{}{"token": token}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -518,14 +521,8 @@ func pathSegmentExists(path, prefix string) bool {
|
||||
|
||||
func firstPathSegmentAfter(path, prefix string) string {
|
||||
path = normalizeResolvePath(path)
|
||||
if !strings.HasPrefix(path, prefix) {
|
||||
return ""
|
||||
}
|
||||
rest := path[len(prefix):]
|
||||
if idx := strings.IndexByte(rest, '/'); idx >= 0 {
|
||||
rest = rest[:idx]
|
||||
}
|
||||
return strings.TrimSpace(rest)
|
||||
token, _ := common.PathSegmentAfterPrefix(path, prefix)
|
||||
return token
|
||||
}
|
||||
|
||||
func valueOrUnknown(s string) string {
|
||||
|
||||
@@ -78,6 +78,7 @@ var urlPathToType = []struct {
|
||||
{"/chat/drive/", "folder"},
|
||||
{"/docx/", "docx"},
|
||||
{"/doc/", "doc"},
|
||||
{"/spreadsheets/", "sheet"},
|
||||
{"/sheets/", "sheet"},
|
||||
{"/base/", "bitable"},
|
||||
{"/bitable/", "bitable"},
|
||||
@@ -117,21 +118,30 @@ func ParseResourceURL(rawURL string) (ResourceRef, bool) {
|
||||
path := u.Path
|
||||
|
||||
for _, mapping := range urlPathToType {
|
||||
if !strings.HasPrefix(path, mapping.Prefix) {
|
||||
continue
|
||||
if token, ok := PathSegmentAfterPrefix(path, mapping.Prefix); ok {
|
||||
return ResourceRef{Type: mapping.Type, Token: token}, true
|
||||
}
|
||||
token := path[len(mapping.Prefix):]
|
||||
// Trim trailing slashes and stop at the next path segment boundary.
|
||||
token = strings.TrimRight(token, "/")
|
||||
if idx := strings.IndexByte(token, '/'); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return ResourceRef{}, false
|
||||
}
|
||||
return ResourceRef{Type: mapping.Type, Token: token}, true
|
||||
}
|
||||
|
||||
return ResourceRef{}, false
|
||||
}
|
||||
|
||||
// PathSegmentAfterPrefix returns the first non-empty path segment after prefix.
|
||||
// It only matches when path itself starts with prefix; query strings and
|
||||
// fragments must be parsed by the caller and are intentionally ignored.
|
||||
func PathSegmentAfterPrefix(path, prefix string) (string, bool) {
|
||||
if !strings.HasPrefix(path, prefix) {
|
||||
return "", false
|
||||
}
|
||||
rest := path[len(prefix):]
|
||||
// Trim trailing slashes and stop at the next path segment boundary.
|
||||
rest = strings.TrimRight(rest, "/")
|
||||
if idx := strings.IndexByte(rest, '/'); idx >= 0 {
|
||||
rest = rest[:idx]
|
||||
}
|
||||
rest = strings.TrimSpace(rest)
|
||||
if rest == "" {
|
||||
return "", false
|
||||
}
|
||||
return rest, true
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ func TestParseResourceURL(t *testing.T) {
|
||||
{"docx", "https://xxx.feishu.cn/docx/doxcnABC", "docx", "doxcnABC", true},
|
||||
{"doc", "https://xxx.feishu.cn/doc/doccnABC", "doc", "doccnABC", true},
|
||||
{"sheet", "https://xxx.feishu.cn/sheets/shtcnABC", "sheet", "shtcnABC", true},
|
||||
{"sheet via /spreadsheets/", "https://xxx.feishu.cn/spreadsheets/shtcnABC", "sheet", "shtcnABC", true},
|
||||
{"bitable via /base/", "https://xxx.feishu.cn/base/bascnABC", "bitable", "bascnABC", true},
|
||||
{"bitable via /bitable/", "https://xxx.feishu.cn/bitable/bascnABC", "bitable", "bascnABC", true},
|
||||
{"wiki", "https://xxx.feishu.cn/wiki/wikcnABC", "wiki", "wikcnABC", true},
|
||||
@@ -41,6 +42,8 @@ func TestParseResourceURL(t *testing.T) {
|
||||
// With query parameters
|
||||
{"with query", "https://xxx.feishu.cn/docx/doxcnABC?from=wiki", "docx", "doxcnABC", true},
|
||||
{"with fragment", "https://xxx.feishu.cn/docx/doxcnABC#section", "docx", "doxcnABC", true},
|
||||
{"query path does not hijack type", "https://xxx.feishu.cn/docx/doxcnABC?next=/wiki/wikcnBAD", "docx", "doxcnABC", true},
|
||||
{"fragment path does not hijack type", "https://xxx.feishu.cn/sheets/shtcnABC#/wiki/wikcnBAD", "sheet", "shtcnABC", true},
|
||||
|
||||
// With trailing slash
|
||||
{"trailing slash", "https://xxx.feishu.cn/docx/doxcnABC/", "docx", "doxcnABC", true},
|
||||
|
||||
@@ -49,7 +49,10 @@ var DocMediaInsert = common.Shortcut{
|
||||
Description: "Insert a local image or file into a Lark document (4-step orchestration + auto-rollback); appends to end by default, or inserts relative to a text selection with --selection-with-ellipsis",
|
||||
Risk: "write",
|
||||
Scopes: []string{"docs:document.media:upload", "docx:document:write_only", "docx:document:readonly"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
ConditionalScopes: []string{
|
||||
"wiki:node:retrieve",
|
||||
},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "file", Desc: "local file path (files > 20MB use multipart upload automatically)"},
|
||||
{Name: "from-clipboard", Type: "bool", Desc: "read image from system clipboard instead of a local file (macOS/Windows built-in; Linux requires xclip, xsel or wl-paste)"},
|
||||
@@ -534,6 +537,9 @@ func resolveDocxDocumentID(runtime *common.RuntimeContext, input string) (string
|
||||
return "", errs.NewValidationError(errs.SubtypeInvalidArgument, "docs +media-insert only supports docx documents; use a docx token/URL or a wiki URL that resolves to docx").WithParam("--doc")
|
||||
case "wiki":
|
||||
fmt.Fprintf(runtime.IO().ErrOut, "Resolving wiki node: %s\n", common.MaskToken(docRef.Token))
|
||||
if err := runtime.EnsureScopes([]string{"wiki:node:retrieve"}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
data, err := runtime.CallAPITyped(
|
||||
"GET",
|
||||
"/open-apis/wiki/v2/spaces/get_node",
|
||||
|
||||
@@ -59,7 +59,10 @@ var DocResourceDownload = common.Shortcut{
|
||||
Description: "Download a document resource (type=cover downloads the cover image content)",
|
||||
Risk: "read",
|
||||
Scopes: []string{"docx:document:readonly", "docs:document.media:download"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
ConditionalScopes: []string{
|
||||
"wiki:node:retrieve",
|
||||
},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "doc", Desc: "document URL or document_id", Required: true},
|
||||
{Name: "type", Default: docCoverResourceType, Desc: "resource type: cover"},
|
||||
@@ -158,7 +161,10 @@ var DocResourceUpdate = common.Shortcut{
|
||||
Description: "Upload and update a document resource (type=cover)",
|
||||
Risk: "write",
|
||||
Scopes: []string{"docx:document:readonly", "docx:document:write_only", "docs:document.media:upload"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
ConditionalScopes: []string{
|
||||
"wiki:node:retrieve",
|
||||
},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "doc", Desc: "document URL or document_id", Required: true},
|
||||
{Name: "type", Default: docCoverResourceType, Desc: "resource type: cover"},
|
||||
@@ -260,7 +266,10 @@ var DocResourceDelete = common.Shortcut{
|
||||
Description: "Delete a document resource (type=cover is idempotent when empty)",
|
||||
Risk: "write",
|
||||
Scopes: []string{"docx:document:readonly", "docx:document:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
ConditionalScopes: []string{
|
||||
"wiki:node:retrieve",
|
||||
},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "doc", Desc: "document URL or document_id", Required: true},
|
||||
{Name: "type", Default: docCoverResourceType, Desc: "resource type: cover"},
|
||||
|
||||
@@ -27,14 +27,13 @@ func parseDocumentRef(input string) (documentRef, error) {
|
||||
return documentRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "--doc cannot be empty").WithParam("--doc")
|
||||
}
|
||||
|
||||
if token, ok := extractDocumentToken(raw, "/wiki/"); ok {
|
||||
return documentRef{Kind: "wiki", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractDocumentToken(raw, "/docx/"); ok {
|
||||
return documentRef{Kind: "docx", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractDocumentToken(raw, "/doc/"); ok {
|
||||
return documentRef{Kind: "doc", Token: token}, nil
|
||||
if ref, ok := common.ParseResourceURL(raw); ok {
|
||||
switch ref.Type {
|
||||
case "wiki", "docx", "doc":
|
||||
return documentRef{Kind: ref.Type, Token: ref.Token}, nil
|
||||
default:
|
||||
return documentRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported --doc input %q: use a docx URL/token or a wiki URL that resolves to docx", raw).WithParam("--doc")
|
||||
}
|
||||
}
|
||||
if strings.Contains(raw, "://") {
|
||||
return documentRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported --doc input %q: use a docx URL/token or a wiki URL that resolves to docx", raw).WithParam("--doc")
|
||||
@@ -46,22 +45,6 @@ func parseDocumentRef(input string) (documentRef, error) {
|
||||
return documentRef{Kind: "docx", Token: raw}, nil
|
||||
}
|
||||
|
||||
func extractDocumentToken(raw, marker string) (string, bool) {
|
||||
idx := strings.Index(raw, marker)
|
||||
if idx < 0 {
|
||||
return "", false
|
||||
}
|
||||
token := raw[idx+len(marker):]
|
||||
if end := strings.IndexAny(token, "/?#"); end >= 0 {
|
||||
token = token[:end]
|
||||
}
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return "", false
|
||||
}
|
||||
return token, true
|
||||
}
|
||||
|
||||
// doDocAPI executes an OpenAPI request against the docs_ai endpoints and returns
|
||||
// the parsed "data" field from the standard Lark response envelope {code, msg, data}.
|
||||
// CallAPITyped lifts the x-tt-logid response header onto the typed error so log_id
|
||||
|
||||
@@ -31,6 +31,18 @@ func TestParseDocumentRef(t *testing.T) {
|
||||
wantKind: "wiki",
|
||||
wantToken: "xxxxxx",
|
||||
},
|
||||
{
|
||||
name: "query path does not hijack docx url",
|
||||
input: "https://example.larksuite.com/docx/doxcn123?next=/wiki/wikcnBAD",
|
||||
wantKind: "docx",
|
||||
wantToken: "doxcn123",
|
||||
},
|
||||
{
|
||||
name: "fragment path does not hijack docx url",
|
||||
input: "https://example.larksuite.com/docx/doxcn123#/wiki/wikcnBAD",
|
||||
wantKind: "docx",
|
||||
wantToken: "doxcn123",
|
||||
},
|
||||
{
|
||||
name: "doc url",
|
||||
input: "https://example.larksuite.com/doc/xxxxxx",
|
||||
|
||||
@@ -129,7 +129,8 @@ var DriveAddComment = common.Shortcut{
|
||||
"docs:document.comment:create",
|
||||
"docs:document.comment:write_only",
|
||||
},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
ConditionalScopes: []string{"wiki:node:retrieve"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
Flags: []common.Flag{
|
||||
{Name: "doc", Desc: "document URL/token, file URL/token, sheet/slides/base/bitable URL, or wiki URL that resolves to doc/docx/file/sheet/slides/base(bitable)", Required: true},
|
||||
{Name: "type", Desc: "document type: doc, docx, file, sheet, slides, bitable, base (required when --doc is a bare token; auto-detected for URLs; use bitable as the wire value, base is accepted as a compatibility alias)", Enum: []string{"doc", "docx", "file", "sheet", "slides", "bitable", "base"}},
|
||||
@@ -515,29 +516,17 @@ func parseCommentDocRef(input, docType string) (commentDocRef, error) {
|
||||
return commentDocRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "--doc cannot be empty").WithParam("--doc")
|
||||
}
|
||||
|
||||
if token, ok := extractURLToken(raw, "/wiki/"); ok {
|
||||
return commentDocRef{Kind: "wiki", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/sheets/"); ok {
|
||||
return commentDocRef{Kind: "sheet", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/base/"); ok {
|
||||
return commentDocRef{Kind: "base", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/bitable/"); ok {
|
||||
return commentDocRef{Kind: "base", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/file/"); ok {
|
||||
return commentDocRef{Kind: "file", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/slides/"); ok {
|
||||
return commentDocRef{Kind: "slides", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/docx/"); ok {
|
||||
return commentDocRef{Kind: "docx", Token: token}, nil
|
||||
}
|
||||
if token, ok := extractURLToken(raw, "/doc/"); ok {
|
||||
return commentDocRef{Kind: "doc", Token: token}, nil
|
||||
if ref, ok := common.ParseResourceURL(raw); ok {
|
||||
kind := ref.Type
|
||||
if kind == "bitable" {
|
||||
kind = "base"
|
||||
}
|
||||
switch kind {
|
||||
case "wiki", "sheet", "base", "file", "slides", "docx", "doc":
|
||||
return commentDocRef{Kind: kind, Token: ref.Token}, nil
|
||||
default:
|
||||
return commentDocRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported --doc input %q: use a doc/docx/file/sheet/slides/base/bitable URL, a token with --type, or a wiki URL that resolves to doc/docx/file/sheet/slides/base(bitable)", raw).WithParam("--doc")
|
||||
}
|
||||
}
|
||||
if strings.Contains(raw, "://") {
|
||||
return commentDocRef{}, errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported --doc input %q: use a doc/docx/file/sheet/slides/base/bitable URL, a token with --type, or a wiki URL that resolves to doc/docx/file/sheet/slides/base(bitable)", raw).WithParam("--doc")
|
||||
@@ -583,6 +572,9 @@ func resolveCommentTarget(ctx context.Context, runtime *common.RuntimeContext, i
|
||||
}
|
||||
|
||||
fmt.Fprintf(runtime.IO().ErrOut, "Resolving wiki node: %s\n", common.MaskToken(docRef.Token))
|
||||
if err := runtime.EnsureScopes([]string{"wiki:node:retrieve"}); err != nil {
|
||||
return resolvedCommentTarget{}, err
|
||||
}
|
||||
data, err := runtime.CallAPITyped(
|
||||
"GET",
|
||||
"/open-apis/wiki/v2/spaces/get_node",
|
||||
@@ -1257,19 +1249,3 @@ func executeSlidesComment(runtime *common.RuntimeContext, docRef commentDocRef)
|
||||
runtime.Out(out, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractURLToken(raw, marker string) (string, bool) {
|
||||
idx := strings.Index(raw, marker)
|
||||
if idx < 0 {
|
||||
return "", false
|
||||
}
|
||||
token := raw[idx+len(marker):]
|
||||
if end := strings.IndexAny(token, "/?#"); end >= 0 {
|
||||
token = token[:end]
|
||||
}
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" {
|
||||
return "", false
|
||||
}
|
||||
return token, true
|
||||
}
|
||||
|
||||
@@ -98,6 +98,18 @@ func TestParseCommentDocRef(t *testing.T) {
|
||||
wantKind: "wiki",
|
||||
wantToken: "xxxxxx",
|
||||
},
|
||||
{
|
||||
name: "query path does not hijack docx url",
|
||||
input: "https://example.larksuite.com/docx/doxcn123?next=/wiki/wikcnBAD",
|
||||
wantKind: "docx",
|
||||
wantToken: "doxcn123",
|
||||
},
|
||||
{
|
||||
name: "fragment path does not hijack sheet url",
|
||||
input: "https://example.larksuite.com/sheets/sht123#/wiki/wikcnBAD",
|
||||
wantKind: "sheet",
|
||||
wantToken: "sht123",
|
||||
},
|
||||
{
|
||||
name: "raw token with type docx",
|
||||
input: "xxxxxx",
|
||||
@@ -2080,14 +2092,6 @@ func TestParseCommentDocRefPathLikeToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractURLTokenEmptyAfterMarker(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, ok := extractURLToken("https://example.com/sheets/", "/sheets/")
|
||||
if ok {
|
||||
t.Fatal("expected false for empty token after marker")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSheetCommentExecuteAPIError(t *testing.T) {
|
||||
f, _, _, reg := cmdutil.TestFactory(t, driveTestConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
|
||||
@@ -20,24 +20,6 @@ var permApplyTypes = []string{
|
||||
"mindnote", "slides",
|
||||
}
|
||||
|
||||
// permApplyURLMarkers maps document URL path markers to the `type` value the
|
||||
// apply-permission endpoint expects. Markers are disjoint strings (each begins
|
||||
// with "/" and ends with "/"), so a simple substring scan disambiguates them.
|
||||
var permApplyURLMarkers = []struct {
|
||||
Marker string
|
||||
Type string
|
||||
}{
|
||||
{"/wiki/", "wiki"},
|
||||
{"/docx/", "docx"},
|
||||
{"/sheets/", "sheet"},
|
||||
{"/base/", "bitable"},
|
||||
{"/bitable/", "bitable"},
|
||||
{"/file/", "file"},
|
||||
{"/mindnote/", "mindnote"},
|
||||
{"/slides/", "slides"},
|
||||
{"/doc/", "doc"},
|
||||
}
|
||||
|
||||
// resolvePermApplyTarget extracts (token, type) from a user-supplied --token
|
||||
// value that may be either a bare token or a full document URL, plus an
|
||||
// optional explicit --type. Explicit --type wins over URL inference.
|
||||
@@ -48,21 +30,17 @@ func resolvePermApplyTarget(raw, explicitType string) (token, docType string, er
|
||||
}
|
||||
|
||||
if strings.Contains(raw, "://") {
|
||||
for _, m := range permApplyURLMarkers {
|
||||
if tok, ok := extractURLToken(raw, m.Marker); ok {
|
||||
token = tok
|
||||
if explicitType == "" {
|
||||
docType = m.Type
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
ref, ok := common.ParseResourceURL(raw)
|
||||
if !ok {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument,
|
||||
"could not infer token from URL %q: supported paths are /docx/, /sheets/, /base/, /bitable/, /file/, /wiki/, /doc/, /mindnote/, /slides/. Pass a bare token with --type instead if the URL shape is unusual",
|
||||
raw,
|
||||
).WithParam("--token")
|
||||
}
|
||||
token = ref.Token
|
||||
if explicitType == "" {
|
||||
docType = ref.Type
|
||||
}
|
||||
} else {
|
||||
token = raw
|
||||
}
|
||||
@@ -76,9 +54,25 @@ func resolvePermApplyTarget(raw, explicitType string) (token, docType string, er
|
||||
strings.Join(permApplyTypes, ", "),
|
||||
).WithParam("--type")
|
||||
}
|
||||
if !isPermApplyType(docType) {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument,
|
||||
"unsupported --type %q; accepted values: %s",
|
||||
docType,
|
||||
strings.Join(permApplyTypes, ", "),
|
||||
).WithParam("--type")
|
||||
}
|
||||
return token, docType, nil
|
||||
}
|
||||
|
||||
func isPermApplyType(docType string) bool {
|
||||
for _, allowed := range permApplyTypes {
|
||||
if docType == allowed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DriveApplyPermission applies to the document owner for view or edit access
|
||||
// on behalf of the invoking user. Matches the open-apis endpoint
|
||||
// /open-apis/drive/v1/permissions/:token/members/apply.
|
||||
|
||||
@@ -42,6 +42,7 @@ func TestResolvePermApplyTarget_URLInference(t *testing.T) {
|
||||
wantType string
|
||||
}{
|
||||
{"docx", "https://example.feishu.cn/docx/doxTok123?from=share", "doxTok123", "docx"},
|
||||
{"query path does not hijack docx url", "https://example.feishu.cn/docx/doxTok123?next=/wiki/wikTokBAD", "doxTok123", "docx"},
|
||||
{"sheets", "https://example.feishu.cn/sheets/shtTok456?sheet=abc", "shtTok456", "sheet"},
|
||||
{"base", "https://example.feishu.cn/base/bscTok789", "bscTok789", "bitable"},
|
||||
{"bitable", "https://example.feishu.cn/bitable/bscTok789", "bscTok789", "bitable"},
|
||||
@@ -86,6 +87,14 @@ func TestResolvePermApplyTarget_UnrecognizedURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePermApplyTarget_UnsupportedURLType(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, _, err := resolvePermApplyTarget("https://example.feishu.cn/drive/folder/fldTok123", "")
|
||||
if err == nil || !strings.Contains(err.Error(), "unsupported --type") {
|
||||
t.Fatalf("expected unsupported type error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePermApplyTarget_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, _, err := resolvePermApplyTarget(" ", "docx")
|
||||
|
||||
Reference in New Issue
Block a user