// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package event import ( "context" "errors" "strings" "testing" "github.com/larksuite/cli/errs" "github.com/larksuite/cli/internal/cmdutil" "github.com/larksuite/cli/internal/credential" ) func TestParseParams(t *testing.T) { cases := []struct { name string in []string want map[string]string wantSentry error wantEcho string }{ { name: "empty input", in: nil, want: map[string]string{}, }, { name: "single key=value", in: []string{"mailbox=user@example.com"}, want: map[string]string{"mailbox": "user@example.com"}, }, { name: "multiple pairs", in: []string{"a=1", "b=2", "c=3"}, want: map[string]string{"a": "1", "b": "2", "c": "3"}, }, { name: "value containing = is kept intact", in: []string{"filter=foo=bar"}, want: map[string]string{"filter": "foo=bar"}, }, { name: "empty value allowed", in: []string{"key="}, want: map[string]string{"key": ""}, }, { name: "duplicate key — last wins", in: []string{"k=1", "k=2"}, want: map[string]string{"k": "2"}, }, { name: "missing = separator", in: []string{"mailbox"}, wantSentry: errInvalidParamFormat, wantEcho: `"mailbox"`, }, { name: "leading = (empty key)", in: []string{"=value"}, wantSentry: errInvalidParamFormat, wantEcho: `"=value"`, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, err := parseParams(tc.in) if tc.wantSentry != nil { if err == nil { t.Fatalf("want error wrapping %v, got nil", tc.wantSentry) } if !errors.Is(err, tc.wantSentry) { t.Fatalf("want errors.Is(err, %v), got %q", tc.wantSentry, err.Error()) } if tc.wantEcho != "" && !strings.Contains(err.Error(), tc.wantEcho) { t.Errorf("err %q should echo %q so user sees the bad input", err.Error(), tc.wantEcho) } assertInvalidArgumentParam(t, err, "--param") return } if err != nil { t.Fatalf("unexpected error: %v", err) } if len(got) != len(tc.want) { t.Fatalf("len = %d, want %d; got=%v", len(got), len(tc.want), got) } for k, v := range tc.want { if got[k] != v { t.Errorf("key %q: got %q, want %q", k, got[k], v) } } }) } } // emptyTokenResolver resolves to a result that carries no token. type emptyTokenResolver struct{} func (emptyTokenResolver) ResolveToken(_ context.Context, _ credential.TokenSpec) (*credential.TokenResult, error) { return &credential.TokenResult{}, nil } // failingTokenResolver fails outright with an untyped error. type failingTokenResolver struct{} func (failingTokenResolver) ResolveToken(_ context.Context, _ credential.TokenSpec) (*credential.TokenResult, error) { return nil, errors.New("backend unavailable") } func factoryWithResolver(r credential.DefaultTokenResolver) *cmdutil.Factory { return &cmdutil.Factory{Credential: credential.NewCredentialProvider(nil, nil, r, nil)} } func TestResolveTenantToken_EmptyTokenResult(t *testing.T) { _, err := resolveTenantToken(context.Background(), factoryWithResolver(emptyTokenResolver{}), "cli_x") if err == nil { t.Fatal("expected error, got nil") } p, ok := errs.ProblemOf(err) if !ok { t.Fatalf("expected typed errs error, got %T: %v", err, err) } if p.Category != errs.CategoryAuthentication || p.Subtype != errs.SubtypeTokenMissing { t.Errorf("problem = %s/%s, want %s/%s", p.Category, p.Subtype, errs.CategoryAuthentication, errs.SubtypeTokenMissing) } var malformed *credential.MalformedTokenResultError if !errors.As(err, &malformed) { t.Error("empty-token failure should preserve the credential-layer cause") } } func TestResolveTenantToken_ResolverFailure(t *testing.T) { _, err := resolveTenantToken(context.Background(), factoryWithResolver(failingTokenResolver{}), "cli_x") if err == nil { t.Fatal("expected error, got nil") } p, ok := errs.ProblemOf(err) if !ok { t.Fatalf("expected typed errs error, got %T: %v", err, err) } if p.Category != errs.CategoryAuthentication || p.Subtype != errs.SubtypeTokenMissing { t.Errorf("problem = %s/%s, want %s/%s", p.Category, p.Subtype, errs.CategoryAuthentication, errs.SubtypeTokenMissing) } if errors.Unwrap(err) == nil { t.Error("resolver failure should preserve its cause") } } // assertInvalidArgumentParam verifies err is a typed validation error with // subtype invalid_argument naming the given flag in its param field. func assertInvalidArgumentParam(t *testing.T, err error, param string) { t.Helper() var ve *errs.ValidationError if !errors.As(err, &ve) { t.Fatalf("expected *errs.ValidationError, got %T: %v", err, err) } if ve.Subtype != errs.SubtypeInvalidArgument { t.Errorf("subtype = %s, want %s", ve.Subtype, errs.SubtypeInvalidArgument) } if ve.Param != param { t.Errorf("param = %q, want %q", ve.Param, param) } } func TestSanitizeOutputDir(t *testing.T) { cases := []struct { name string in string wantSentry error }{ { name: "relative path accepted", in: "./output", }, { name: "nested relative path accepted", in: "events/today", }, { name: "tilde rejected explicitly", in: "~/events", wantSentry: errOutputDirTilde, }, { name: "parent escape rejected", in: "../outside", wantSentry: errOutputDirUnsafe, }, { name: "absolute path rejected", in: "/tmp/events", wantSentry: errOutputDirUnsafe, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got, err := sanitizeOutputDir(tc.in) if tc.wantSentry != nil { if err == nil { t.Fatalf("want error wrapping %v, got nil (path=%q)", tc.wantSentry, got) } if !errors.Is(err, tc.wantSentry) { t.Fatalf("want errors.Is(err, %v), got %q", tc.wantSentry, err.Error()) } assertInvalidArgumentParam(t, err, "--output-dir") return } if err != nil { t.Fatalf("unexpected error: %v", err) } if got == "" { t.Errorf("expected non-empty safe path, got %q", got) } }) } }