Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions shortcuts/base/base_resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ func executeBaseURLResolve(runtime *common.RuntimeContext) error {
switch classifyBaseURL(parsed) {
case "base_url":
out := resolveBaseURL(parsed)
enrichBaseResolveHint(runtime, out, resolveBaseURLSelection(parsed))
if err := enrichBaseResolveHint(runtime, out, resolveBaseURLSelection(parsed)); err != nil {
return err
}
runtime.OutFormat(out, nil, nil)
return nil
case "wiki_url":
Expand All @@ -205,7 +207,9 @@ func executeBaseURLResolve(runtime *common.RuntimeContext) error {
}
selection := resolveBaseURLSelection(parsed)
applyBaseURLSelection(out, selection)
enrichBaseResolveHint(runtime, out, selection)
if err := enrichBaseResolveHint(runtime, out, selection); err != nil {
return err
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
runtime.OutFormat(out, nil, nil)
return nil
case "record_share_url":
Expand Down Expand Up @@ -422,15 +426,19 @@ func executeBaseTitleResolve(runtime *common.RuntimeContext) error {
}
}

func enrichBaseResolveHint(runtime *common.RuntimeContext, out map[string]interface{}, selection baseURLSelection) {
func enrichBaseResolveHint(runtime *common.RuntimeContext, out map[string]interface{}, selection baseURLSelection) error {
baseToken := strings.TrimSpace(common.GetString(out, "base_token"))
selectedBlockID := strings.TrimSpace(common.GetString(out, "block_id"))
if baseToken == "" || selectedBlockID == "" {
out["hint"] = resolveHint("", nil)
return
return nil
}

if block, found, err := resolveSelectedBaseBlock(runtime, baseToken, selectedBlockID); err == nil && found {
block, found, err := resolveSelectedBaseBlock(runtime, baseToken, selectedBlockID)
if err != nil {
return err
}
if found {
out["block_type"] = block.Type
if block.Name != "" {
out["block_name"] = block.Name
Expand Down Expand Up @@ -467,10 +475,11 @@ func enrichBaseResolveHint(runtime *common.RuntimeContext, out map[string]interf
default:
out["hint"] = resolveUnknownBlockHint()
}
return
return nil
}

out["hint"] = resolveUnknownBlockHint()
return nil
}

type resolvedBaseBlock struct {
Expand Down
71 changes: 70 additions & 1 deletion shortcuts/base/base_resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package base

import (
"errors"
"net/http"
"strings"
"testing"
Expand Down Expand Up @@ -67,7 +68,10 @@ func TestBaseURLResolveBaseURL(t *testing.T) {
})

t.Run("unconfirmed selected block stays neutral", func(t *testing.T) {
factory, stdout, _ := newExecuteFactory(t)
factory, stdout, reg := newExecuteFactory(t)
reg.Register(baseBlockListResolveStub("bas123",
map[string]interface{}{"id": "tbl_other", "type": "table", "name": "Other"},
))
err := runShortcutWithAuthTypes(t, BaseURLResolve, authTypes(), []string{
"+url-resolve", "--url", "https://example.larkoffice.com/base/bas123?table=tbl123&view=vew_stale&record=rec_stale", "--as", "user",
}, factory, stdout)
Expand Down Expand Up @@ -96,6 +100,15 @@ func TestBaseURLResolveBaseURL(t *testing.T) {
}
})

t.Run("block list error is returned", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
reg.Register(baseBlockListScopeErrorStub("bas123"))
err := runShortcutWithAuthTypes(t, BaseURLResolve, authTypes(), []string{
"+url-resolve", "--url", "https://example.larkoffice.com/base/bas123?table=tbl123&view=vew_stale", "--as", "bot",
}, factory, stdout)
assertBaseBlockReadPermissionError(t, err, stdout)
})

t.Run("field endpoint does not confirm untyped block", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
reg.Register(baseBlockListResolveStub("bas123",
Expand Down Expand Up @@ -269,6 +282,22 @@ func baseBlockListResolveStub(baseToken string, blocks ...map[string]interface{}
}
}

func baseBlockListScopeErrorStub(baseToken string) *httpmock.Stub {
return &httpmock.Stub{
Method: "POST",
URL: "/open-apis/base/v3/bases/" + baseToken + "/blocks/list",
Body: map[string]interface{}{
"code": 99991672,
"msg": "access denied",
"error": map[string]interface{}{
"permission_violations": []interface{}{
map[string]interface{}{"subject": "base:block:read"},
},
},
},
}
}

func TestBaseURLResolveWikiURL(t *testing.T) {
t.Run("bitable", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
Expand Down Expand Up @@ -309,6 +338,19 @@ func TestBaseURLResolveWikiURL(t *testing.T) {
}
})

t.Run("bitable table coordinates return block list error", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
reg.Register(wikiBaseNodeStub("wik123", "bas123", "Demo Base"))
reg.Register(baseBlockListScopeErrorStub("bas123"))

err := runShortcutWithAuthTypes(t, BaseURLResolve, authTypes(), []string{
"+url-resolve",
"--url", "https://example.larkoffice.com/wiki/wik123?table=tbl123&view=vew_stale",
"--as", "bot",
}, factory, stdout)
assertBaseBlockReadPermissionError(t, err, stdout)
})

t.Run("bitable with dashboard selection", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
reg.Register(wikiBaseNodeStub("wik123", "bas123", "Demo Base"))
Expand Down Expand Up @@ -376,6 +418,33 @@ func wikiBaseNodeStub(wikiToken, baseToken, title string) *httpmock.Stub {
}
}

func assertBaseBlockReadPermissionError(t *testing.T, err error, stdout interface {
Len() int
String() string
}) {
t.Helper()
if err == nil {
t.Fatal("expected block-list error to be returned")
}
p, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("expected typed problem, got %T %v", err, err)
}
if p.Category != errs.CategoryAuthorization || p.Subtype != errs.SubtypeAppScopeNotApplied || p.Code != 99991672 {
t.Fatalf("unexpected problem: %#v", p)
}
var permissionErr *errs.PermissionError
if !errors.As(err, &permissionErr) {
t.Fatalf("expected PermissionError, got %T %v", err, err)
}
if len(permissionErr.MissingScopes) != 1 || permissionErr.MissingScopes[0] != "base:block:read" {
t.Fatalf("missing scopes=%v", permissionErr.MissingScopes)
}
if stdout.Len() != 0 {
t.Fatalf("stdout should stay empty when block-list fails: %s", stdout.String())
}
}

func TestBaseURLResolveRecordShareURL(t *testing.T) {
t.Run("enriched", func(t *testing.T) {
factory, stdout, reg := newExecuteFactory(t)
Expand Down
Loading