Files
larksuite-cli/shortcuts/common/resource_url_test.go

150 lines
6.3 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package common
import (
"testing"
"github.com/larksuite/cli/internal/core"
)
func TestParseResourceURL(t *testing.T) {
t.Parallel()
tests := []struct {
name string
rawURL string
wantType string
wantToken string
wantOK bool
}{
// All 9 supported types
{"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},
{"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},
{"file", "https://xxx.feishu.cn/file/boxcnABC", "file", "boxcnABC", true},
{"folder", "https://xxx.feishu.cn/drive/folder/fldcnABC", "folder", "fldcnABC", true},
{"file via /drive/file/", "https://feishu.doubao.com/drive/file/boxcnABC", "file", "boxcnABC", true},
{"folder via /chat/drive/", "https://feishu.doubao.com/chat/drive/fldcnABC", "folder", "fldcnABC", true},
{"folder via /drive/shr/", "https://feishu.doubao.com/drive/shr/fldcnABC", "folder", "fldcnABC", true},
{"mindnote", "https://xxx.feishu.cn/mindnote/mncnABC", "mindnote", "mncnABC", true},
{"slides", "https://xxx.feishu.cn/slides/slkcnABC", "slides", "slkcnABC", true},
// Lark domain
{"lark docx", "https://xxx.larksuite.com/docx/doxcnABC", "docx", "doxcnABC", true},
{"lark wiki", "https://xxx.larksuite.com/wiki/wikcnABC", "wiki", "wikcnABC", true},
// 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},
// With trailing slash
{"trailing slash", "https://xxx.feishu.cn/docx/doxcnABC/", "docx", "doxcnABC", true},
// With extra path segments after token
{"extra path", "https://xxx.feishu.cn/docx/doxcnABC/edit", "docx", "doxcnABC", true},
// Non-Lark host with Lark-like path (host validation is the caller's responsibility)
{"non-lark host with lark path", "https://google.com/docx/doxcnABC", "docx", "doxcnABC", true},
// Negative cases
{"unrecognized path", "https://xxx.feishu.cn/calendar/calABC", "", "", false},
{"non-lark host unrecognized path", "https://example.com/page", "", "", false},
{"empty input", "", "", "", false},
{"bare token", "doxcnABC", "", "", false},
{"invalid url parse", "://not-a-valid-url", "", "", false},
{"matching prefix but empty token", "https://xxx.feishu.cn/docx/", "", "", false},
{"matching prefix but whitespace-only token", "https://xxx.feishu.cn/docx/ ", "", "", false},
{"whitespace-only input", " ", "", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ref, ok := ParseResourceURL(tt.rawURL)
if ok != tt.wantOK {
t.Errorf("ParseResourceURL(%q) ok = %v, want %v", tt.rawURL, ok, tt.wantOK)
}
if ok {
if ref.Type != tt.wantType {
t.Errorf("ParseResourceURL(%q) Type = %q, want %q", tt.rawURL, ref.Type, tt.wantType)
}
if ref.Token != tt.wantToken {
t.Errorf("ParseResourceURL(%q) Token = %q, want %q", tt.rawURL, ref.Token, tt.wantToken)
}
}
})
}
}
// TestParseResourceURL_RoundTrip verifies that ParseResourceURL is the inverse
// of BuildResourceURL for all supported types.
func TestParseResourceURL_RoundTrip(t *testing.T) {
t.Parallel()
types := []string{"docx", "doc", "sheet", "bitable", "wiki", "file", "folder", "mindnote", "slides"}
token := "testTOKEN123"
for _, kind := range types {
t.Run(kind, func(t *testing.T) {
built := BuildResourceURL(core.BrandFeishu, kind, token)
if built == "" {
t.Fatalf("BuildResourceURL returned empty for kind %q", kind)
}
ref, ok := ParseResourceURL(built)
if !ok {
t.Fatalf("ParseResourceURL(%q) returned ok=false", built)
}
if ref.Type != kind {
t.Errorf("round-trip type mismatch: got %q, want %q", ref.Type, kind)
}
if ref.Token != token {
t.Errorf("round-trip token mismatch: got %q, want %q", ref.Token, token)
}
})
}
}
func TestBuildResourceURL(t *testing.T) {
t.Parallel()
tests := []struct {
name string
brand core.LarkBrand
kind string
token string
want string
}{
{"feishu docx", core.BrandFeishu, "docx", "doxcnABC", "https://www.feishu.cn/docx/doxcnABC"},
{"feishu doc legacy", core.BrandFeishu, "doc", "doccnABC", "https://www.feishu.cn/doc/doccnABC"},
{"feishu sheet", core.BrandFeishu, "sheet", "shtcnABC", "https://www.feishu.cn/sheets/shtcnABC"},
{"feishu bitable", core.BrandFeishu, "bitable", "bascnABC", "https://www.feishu.cn/base/bascnABC"},
{"feishu wiki", core.BrandFeishu, "wiki", "wikcnABC", "https://www.feishu.cn/wiki/wikcnABC"},
{"feishu file", core.BrandFeishu, "file", "boxcnABC", "https://www.feishu.cn/file/boxcnABC"},
{"feishu folder", core.BrandFeishu, "folder", "fldcnABC", "https://www.feishu.cn/drive/folder/fldcnABC"},
{"feishu mindnote", core.BrandFeishu, "mindnote", "mncnABC", "https://www.feishu.cn/mindnote/mncnABC"},
{"feishu slides", core.BrandFeishu, "slides", "slkcnABC", "https://www.feishu.cn/slides/slkcnABC"},
{"lark docx", core.BrandLark, "docx", "doxcnABC", "https://www.larksuite.com/docx/doxcnABC"},
{"lark wiki", core.BrandLark, "wiki", "wikcnABC", "https://www.larksuite.com/wiki/wikcnABC"},
{"empty brand defaults to feishu", core.LarkBrand(""), "docx", "doxcnABC", "https://www.feishu.cn/docx/doxcnABC"},
{"kind case-insensitive", core.BrandFeishu, "DOCX", "doxcnABC", "https://www.feishu.cn/docx/doxcnABC"},
{"token whitespace trimmed", core.BrandFeishu, "docx", " doxcnABC ", "https://www.feishu.cn/docx/doxcnABC"},
{"empty token", core.BrandFeishu, "docx", "", ""},
{"whitespace-only token", core.BrandFeishu, "docx", " ", ""},
{"unknown kind", core.BrandFeishu, "calendar", "calABC", ""},
{"empty kind", core.BrandFeishu, "", "doxcnABC", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := BuildResourceURL(tt.brand, tt.kind, tt.token)
if got != tt.want {
t.Errorf("BuildResourceURL(%q, %q, %q) = %q, want %q", tt.brand, tt.kind, tt.token, got, tt.want)
}
})
}
}