Files
larksuite-cli/internal/qualitygate/rules/errorfacts_test.go
2026-06-17 16:29:33 +08:00

783 lines
22 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package rules
import (
"path/filepath"
"testing"
"github.com/larksuite/cli/internal/qualitygate/facts"
"github.com/larksuite/cli/internal/qualitygate/manifest"
"github.com/larksuite/cli/internal/qualitygate/report"
"github.com/larksuite/cli/internal/vfs"
)
func TestCollectErrorFactsMarksHelperBareErrorAsNonBoundaryWarning(t *testing.T) {
src := `package demo
import "fmt"
func parseTimeValue(s string) error {
return fmt.Errorf("invalid timestamp %q", s)
}`
facts, diags := CollectErrorFacts("cmd/demo.go", src, BoundaryIndex{})
if len(facts) != 1 {
t.Fatalf("got %d facts", len(facts))
}
if facts[0].Boundary {
t.Fatalf("helper bare error must not be marked boundary")
}
if len(diags) != 1 || diags[0].Rule != "no_bare_helper_error" || diags[0].Action != report.ActionWarning {
t.Fatalf("helper bare error should warn only, got %#v", diags)
}
}
func TestCollectErrorFactsCountsHintActions(t *testing.T) {
hint := "run `lark-cli docs +fetch --doc abc` with --api-version v2"
if got := HintActionCount(hint); got < 2 {
t.Fatalf("HintActionCount() = %d, want at least 2", got)
}
}
func TestHintActionCountDoesNotCountIdentifierSuffixes(t *testing.T) {
for _, hint := range []string{
"provide file_token in the input",
"missing open_id",
"not_found",
} {
if got := HintActionCount(hint); got != 0 {
t.Fatalf("HintActionCount(%q) = %d, want 0", hint, got)
}
}
}
func TestHintActionCountCountsLocaleToken(t *testing.T) {
if got := HintActionCount("set locale to zh_CN"); got != 1 {
t.Fatalf("HintActionCount() = %d, want 1", got)
}
}
func TestCollectRepoErrorFactsAnnotatesShortcutBoundaryScope(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
path := filepath.Join(repo, "shortcuts", "wiki", "move.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package wiki
import (
"context"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/shortcuts/common"
)
var WikiMove = common.Shortcut{
Service: "wiki",
Command: "+move",
Execute: executeWikiMove,
}
func executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
return output.ErrWithHint("invalid_input", "validation", "missing token", "run lark-cli wiki +move --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts", len(errorFacts))
}
if !errorFacts[0].Boundary || errorFacts[0].Command != "wiki +move" {
t.Fatalf("boundary command not annotated: %#v", errorFacts[0])
}
got := facts.Build(
manifest.Manifest{Commands: []manifest.Command{{Path: "wiki +move", Domain: "wiki", Source: manifest.SourceShortcut}}},
nil,
nil,
errorFacts,
nil,
nil,
nil,
map[string]bool{"shortcuts/wiki/move.go": true},
)
if got.Errors[0].CommandPath != "wiki +move" || got.Errors[0].Domain != "wiki" || got.Errors[0].Source != "shortcut" || !got.Errors[0].Changed {
t.Fatalf("error fact scope not enriched: %#v", got.Errors[0])
}
}
func TestCollectErrorFactsTreatsCommonValidationErrorfAsStructuredBoundary(t *testing.T) {
src := `package contact
import (
"context"
"github.com/larksuite/cli/shortcuts/common"
)
var ContactGetUser = common.Shortcut{
Service: "contact",
Command: "+get-user",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return common.ValidationErrorf("invalid --user-id-type").
WithHint("the identifier type is unsupported").
WithParam("--user-id-type")
},
}
`
path := "shortcuts/contact/contact_get_user.go"
errorFacts, diags := CollectErrorFacts(path, src, BuildErrorBoundaryIndex(path, src))
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics: %#v", diags)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts, want 1", len(errorFacts))
}
got := errorFacts[0]
if !got.Boundary || got.Command != "contact +get-user" {
t.Fatalf("common.ValidationErrorf boundary not annotated: %#v", got)
}
if !got.UsesStructuredError || !got.HasHint || !got.RequiredHint {
t.Fatalf("common.ValidationErrorf metadata not structured with required hint: %#v", got)
}
if got.HintActionCount != 0 {
t.Fatalf("HintActionCount = %d, want 0 for non-actionable hint", got.HintActionCount)
}
}
func TestCollectErrorFactsTracksCommonTypedValidatorMultiReturnBoundary(t *testing.T) {
src := `package minutes
import (
"context"
"github.com/larksuite/cli/shortcuts/common"
)
var MinutesSearch = common.Shortcut{
Service: "minutes",
Command: "+search",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 50, 1, 100); err != nil {
return err
}
return nil
},
}
`
path := "shortcuts/minutes/minutes_search.go"
errorFacts, diags := CollectErrorFacts(path, src, BuildErrorBoundaryIndex(path, src))
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics: %#v", diags)
}
got, ok := findErrorFact(errorFacts, path, "common.ValidatePageSizeTyped")
if !ok {
t.Fatalf("common typed validator boundary fact not found: %#v", errorFacts)
}
if !got.Boundary || got.Command != "minutes +search" || !got.UsesStructuredError {
t.Fatalf("common typed validator boundary not annotated: %#v", got)
}
}
func TestCollectErrorFactsTreatsDomainValidationHelperAsStructuredBoundary(t *testing.T) {
src := `package base
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
func baseFlagErrorf(format string, args ...any) error {
return baseValidationErrorf(format, args...)
}
func baseValidationErrorf(format string, args ...any) error {
return errs.NewValidationError(errs.SubtypeInvalidArgument, format, args...)
}
var BaseRoleCreate = common.Shortcut{
Service: "base",
Command: "+role-create",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return baseFlagErrorf("--base-token must not be blank")
},
}
`
path := "shortcuts/base/base_role_create.go"
errorFacts, diags := CollectErrorFacts(path, src, BuildErrorBoundaryIndex(path, src))
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics: %#v", diags)
}
if len(errorFacts) != 3 {
t.Fatalf("got %d error facts, want helper definitions plus boundary call: %#v", len(errorFacts), errorFacts)
}
got := errorFacts[2]
if !got.Boundary || got.Command != "base +role-create" {
t.Fatalf("domain validation helper boundary not annotated: %#v", got)
}
if !got.UsesStructuredError || got.Code != "baseFlagErrorf" {
t.Fatalf("domain validation helper not treated as structured: %#v", got)
}
}
func TestCollectErrorFactsTracksDomainValidateHelperMultiReturnBoundary(t *testing.T) {
src := `package base
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
func baseValidationErrorf(format string, args ...any) error {
return errs.NewValidationError(errs.SubtypeInvalidArgument, format, args...)
}
func validateRoleName(name string) (string, error) {
if name == "" {
return "", baseValidationErrorf("--role-name must not be blank")
}
return name, nil
}
var BaseRoleCreate = common.Shortcut{
Service: "base",
Command: "+role-create",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := validateRoleName(""); err != nil {
return err
}
return nil
},
}
`
path := "shortcuts/base/base_role_create.go"
errorFacts, diags := CollectErrorFacts(path, src, BuildErrorBoundaryIndex(path, src))
if len(diags) != 0 {
t.Fatalf("unexpected diagnostics: %#v", diags)
}
got, ok := findErrorFact(errorFacts, path, "validateRoleName")
if !ok {
t.Fatalf("domain validate helper boundary fact not found: %#v", errorFacts)
}
if !got.Boundary || got.Command != "base +role-create" || !got.UsesStructuredError {
t.Fatalf("domain validate helper boundary not annotated: %#v", got)
}
}
func TestCollectErrorFactsDoesNotTreatOrdinaryMultiReturnAsStructuredBoundary(t *testing.T) {
src := `package base
import (
"context"
"errors"
"github.com/larksuite/cli/shortcuts/common"
)
func parseRoleName(name string) (string, error) {
if name == "" {
return "", errors.New("role name is required")
}
return name, nil
}
var BaseRoleCreate = common.Shortcut{
Service: "base",
Command: "+role-create",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := parseRoleName(""); err != nil {
return err
}
return nil
},
}
`
path := "shortcuts/base/base_role_create.go"
errorFacts, _ := CollectErrorFacts(path, src, BuildErrorBoundaryIndex(path, src))
if got, ok := findErrorFact(errorFacts, path, "parseRoleName"); ok {
t.Fatalf("ordinary multi-return helper should not be treated as structured boundary: %#v", got)
}
}
func TestCollectRepoErrorFactsUsesPackageStructuredHelpersAcrossFiles(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
baseDir := filepath.Join(repo, "shortcuts", "base")
if err := vfs.MkdirAll(baseDir, 0o755); err != nil {
t.Fatalf("mkdir base: %v", err)
}
helperSrc := `package base
import "github.com/larksuite/cli/errs"
func baseFlagErrorf(format string, args ...any) error {
return baseValidationErrorf(format, args...)
}
func baseValidationErrorf(format string, args ...any) error {
return errs.NewValidationError(errs.SubtypeInvalidArgument, format, args...)
}
`
if err := vfs.WriteFile(filepath.Join(baseDir, "base_errors.go"), []byte(helperSrc), 0o644); err != nil {
t.Fatalf("write helper: %v", err)
}
shortcutSrc := `package base
import (
"context"
"github.com/larksuite/cli/shortcuts/common"
)
var BaseRoleCreate = common.Shortcut{
Service: "base",
Command: "+role-create",
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
return baseFlagErrorf("--base-token must not be blank")
},
}
`
if err := vfs.WriteFile(filepath.Join(baseDir, "base_role_create.go"), []byte(shortcutSrc), 0o644); err != nil {
t.Fatalf("write shortcut: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
var found bool
for _, fact := range errorFacts {
if fact.File == "shortcuts/base/base_role_create.go" && fact.Line == 13 {
found = true
if !fact.Boundary || fact.Command != "base +role-create" || !fact.UsesStructuredError {
t.Fatalf("cross-file helper fact not annotated: %#v", fact)
}
}
}
if !found {
t.Fatalf("cross-file helper boundary fact not found: %#v", errorFacts)
}
}
func findErrorFact(errorFacts []facts.ErrorFact, path, code string) (facts.ErrorFact, bool) {
for _, fact := range errorFacts {
if fact.File == path && fact.Code == code {
return fact, true
}
}
return facts.ErrorFact{}, false
}
func TestCollectRepoErrorFactsAnnotatesCobraRunEBoundaryScope(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "shortcuts"), 0o755); err != nil {
t.Fatalf("mkdir shortcuts: %v", err)
}
path := filepath.Join(repo, "cmd", "demo.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package cmd
import (
"github.com/larksuite/cli/errs"
"github.com/spf13/cobra"
)
var demoCmd = &cobra.Command{
Use: "demo [id]",
RunE: runDemo,
}
func runDemo(cmd *cobra.Command, args []string) error {
return errs.NewValidationError("missing demo id").WithHint("run lark-cli demo --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts", len(errorFacts))
}
if !errorFacts[0].Boundary || errorFacts[0].Command != "demo" {
t.Fatalf("cobra RunE boundary command not annotated: %#v", errorFacts[0])
}
}
func TestCollectRepoErrorFactsAnnotatesReturnedLocalBareErrorBoundary(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "shortcuts"), 0o755); err != nil {
t.Fatalf("mkdir shortcuts: %v", err)
}
path := filepath.Join(repo, "cmd", "demo.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var demoCmd = &cobra.Command{
Use: "demo [id]",
RunE: runDemo,
}
func runDemo(cmd *cobra.Command, args []string) error {
err := fmt.Errorf("missing demo id")
return err
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, diags, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts", len(errorFacts))
}
if !errorFacts[0].Boundary || errorFacts[0].Command != "demo" {
t.Fatalf("returned local bare error boundary not annotated: facts=%#v diags=%#v", errorFacts, diags)
}
if len(diags) != 0 {
t.Fatalf("boundary bare error should not also be reported as helper warning: %#v", diags)
}
}
func TestCollectRepoErrorFactsAnnotatesReturnedLocalStructuredErrorBoundary(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "shortcuts"), 0o755); err != nil {
t.Fatalf("mkdir shortcuts: %v", err)
}
path := filepath.Join(repo, "cmd", "demo.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package cmd
import (
"github.com/larksuite/cli/errs"
"github.com/spf13/cobra"
)
var demoCmd = &cobra.Command{
Use: "demo [id]",
RunE: runDemo,
}
func runDemo(cmd *cobra.Command, args []string) error {
err := errs.NewValidationError("missing demo id").WithHint("run lark-cli demo --help")
return err
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, diags, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts", len(errorFacts))
}
if !errorFacts[0].Boundary || errorFacts[0].Command != "demo" || !errorFacts[0].UsesStructuredError || !errorFacts[0].HasHint {
t.Fatalf("returned local structured error boundary not annotated: facts=%#v diags=%#v", errorFacts, diags)
}
if len(diags) != 0 {
t.Fatalf("structured boundary error should not produce helper diagnostics: %#v", diags)
}
}
func TestCollectRepoErrorFactsAnnotatesFluentStructuredErrorBoundary(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
path := filepath.Join(repo, "shortcuts", "wiki", "move.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package wiki
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
var WikiMove = common.Shortcut{
Service: "wiki",
Command: "+move",
Execute: executeWikiMove,
}
func executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
return errs.NewValidationError("missing token").WithParam("node_token").WithHint("run lark-cli wiki +move --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
if len(errorFacts) != 1 {
t.Fatalf("got %d error facts", len(errorFacts))
}
if !errorFacts[0].Boundary || errorFacts[0].Command != "wiki +move" {
t.Fatalf("fluent structured error boundary not annotated: %#v", errorFacts[0])
}
if !errorFacts[0].HasHint || errorFacts[0].HintActionCount == 0 || !errorFacts[0].RequiredHint {
t.Fatalf("fluent structured error hint metadata not annotated: %#v", errorFacts[0])
}
}
func TestCollectRepoErrorFactsDoesNotMarkSameNameMethodBoundary(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
path := filepath.Join(repo, "shortcuts", "wiki", "move.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package wiki
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
type executor struct{}
var WikiMove = common.Shortcut{
Service: "wiki",
Command: "+move",
Execute: executeWikiMove,
}
func executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
return nil
}
func (executor) executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
return errs.NewValidationError("missing token").WithHint("run lark-cli wiki +move --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
for _, fact := range errorFacts {
if fact.Boundary {
t.Fatalf("same-name method must not be marked as command boundary: %#v", errorFacts)
}
}
}
func TestCollectRepoErrorFactsAnnotatesVariableFluentHintBoundary(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
path := filepath.Join(repo, "shortcuts", "wiki", "move.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package wiki
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
var WikiMove = common.Shortcut{
Service: "wiki",
Command: "+move",
Execute: executeWikiMove,
}
func executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
base := errs.NewValidationError("missing token").WithParam("node_token")
return base.WithHint("run lark-cli wiki +move --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
var boundary facts.ErrorFact
for _, fact := range errorFacts {
if fact.Boundary {
boundary = fact
}
}
if boundary.Command != "wiki +move" || !boundary.HasHint || boundary.HintActionCount == 0 || !boundary.RequiredHint {
t.Fatalf("variable fluent hint boundary not annotated: %#v", errorFacts)
}
}
func TestCollectRepoErrorFactsSkipsDeletedChangedFiles(t *testing.T) {
repo := t.TempDir()
errorFacts, diags, err := CollectRepoErrorFacts(repo, []string{"shortcuts/wiki/deleted.go"}, true)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() should skip deleted changed files, got %v", err)
}
if len(errorFacts) != 0 || len(diags) != 0 {
t.Fatalf("deleted changed files should produce no facts or diagnostics, got facts=%#v diags=%#v", errorFacts, diags)
}
}
func TestCollectErrorFactsDoesNotTreatUnknownWithHintAsStructured(t *testing.T) {
src := `package demo
func helper(base customError) error {
return base.WithHint("run lark-cli docs +fetch --doc abc")
}
`
errorFacts, _ := CollectErrorFacts("cmd/demo.go", src, BoundaryIndex{})
if len(errorFacts) != 0 {
t.Fatalf("unknown WithHint receiver should not be collected as structured error: %#v", errorFacts)
}
}
func TestCollectErrorFactsDoesNotLeakStructuredVarsAcrossFunctions(t *testing.T) {
src := `package demo
import "github.com/larksuite/cli/errs"
func other() error {
base := errs.NewValidationError("missing token")
return base
}
func helper(base customError) error {
return base.WithHint("run lark-cli docs +fetch --doc abc")
}
`
errorFacts, _ := CollectErrorFacts("cmd/demo.go", src, BoundaryIndex{})
if len(errorFacts) != 1 || errorFacts[0].Code != "NewValidationError" {
t.Fatalf("only local structured constructor should be collected: %#v", errorFacts)
}
}
func TestCollectErrorFactsDoesNotLeakStructuredVarsAcrossBlocks(t *testing.T) {
src := `package demo
import "github.com/larksuite/cli/errs"
func helper(base customError) error {
if true {
base := errs.NewValidationError("missing token")
_ = base
}
return base.WithHint("run lark-cli docs +fetch --doc abc")
}
`
errorFacts, _ := CollectErrorFacts("cmd/demo.go", src, BoundaryIndex{})
if len(errorFacts) != 1 || errorFacts[0].Code != "NewValidationError" {
t.Fatalf("inner block structured var should not leak to outer receiver: %#v", errorFacts)
}
}
func TestCollectRepoErrorFactsAnnotatesVariableFluentHintThroughWrapper(t *testing.T) {
repo := t.TempDir()
if err := vfs.MkdirAll(filepath.Join(repo, "cmd"), 0o755); err != nil {
t.Fatalf("mkdir cmd: %v", err)
}
path := filepath.Join(repo, "shortcuts", "wiki", "move.go")
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("mkdir: %v", err)
}
src := `package wiki
import (
"context"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/shortcuts/common"
)
var WikiMove = common.Shortcut{
Service: "wiki",
Command: "+move",
Execute: executeWikiMove,
}
func executeWikiMove(ctx context.Context, runtime *common.RuntimeContext) error {
base := errs.NewValidationError("missing token")
wrapped := base.WithParam("node_token")
return wrapped.WithHint("run lark-cli wiki +move --help")
}
`
if err := vfs.WriteFile(path, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}
errorFacts, _, err := CollectRepoErrorFacts(repo, nil, false)
if err != nil {
t.Fatalf("CollectRepoErrorFacts() error = %v", err)
}
var boundary facts.ErrorFact
for _, fact := range errorFacts {
if fact.Boundary {
boundary = fact
}
}
if boundary.Command != "wiki +move" || !boundary.HasHint || boundary.HintActionCount == 0 || !boundary.RequiredHint {
t.Fatalf("wrapped fluent hint boundary not annotated: %#v", errorFacts)
}
}
func TestMarkBoundaryLineInitializesEmptyBoundaryIndex(t *testing.T) {
var idx BoundaryIndex
markBoundaryLine(&idx, "cmd/demo.go", 12, "demo")
command, ok := idx.commandAt("cmd/demo.go", 12)
if !ok || command != "demo" {
t.Fatalf("expected initialized boundary command, got %q ok=%v", command, ok)
}
}