Files
larksuite-cli/shortcuts/common/userids_test.go
evandance fc9f9c1f26 feat(contact +search-user): add search filters and richer profile fields (#648)
* feat(contact +search-user): add search filters and richer profile fields

- Filter results by chat history, employment status, tenant boundary,
  or enterprise email presence; keyword is now optional so filter-only
  queries ("list all my external contacts") work end-to-end.
- Each result now carries multilingual names, contact email, activation
  state, whether you've chatted with them, tenant context, user
  signature, and a hit-highlight line that surfaces the matched segment
  and the user's department path.
- Always-empty legacy columns and fields the new backend no longer
  returns are dropped.
- Also fixes the contact +get-user skill doc, which previously
  instructed callers to pass --table (a flag that never existed); now
  correctly documents --format table and the full --format enum.

* refactor(lark-contact): clean up search-user code, tighten skill docs

- contact_search_user.go / _test.go: simplify and clarify
- SKILL.md: focus description on user-facing trigger scenarios;
  rework decision table; trim notes to load-bearing constraints
- references/lark-contact-search-user.md: add flag table covering
  all four bool filters; add multi-filter examples; clean up
  output field contract (drop server <h> tag implementation detail)
- references/lark-contact-get-user.md: trim to two real use cases
  (self via user identity; full profile of others via bot identity);
  point user-mode-by-id users to +search-user instead
- .golangci.yml: replace package-level deny on net/http with a
  symbol-level forbidigo rule. Constants (http.MethodPost,
  http.StatusOK) and helpers (http.StatusText) were never the
  intent; only Client / NewRequest / Get / Post / Do etc. are now
  blocked in shortcuts/, matching the rule's actual purpose

Change-Id: Ic42043d3f4c1b675800e48229c7ba2e970da26fe

* fix(contact +search-user): align query limit and reject empty user-ids

API rejects queries longer than 50 characters; local cap was 64 runes,
producing confusing "passed local validation but server-rejected"
behaviour. Lower the cap to 50 and rename the constant accordingly.

Also reject --user-ids inputs that parse to zero entries (",,,",
" , , ", ","): SplitCSV silently dropped empty segments, so the
shortcut sent an empty body to the API and returned indeterminate
results.

Change-Id: Ib34fe897023e175bf4c657273bdb49a33d2f083b

---------

Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-04-28 20:03:07 +08:00

78 lines
2.3 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package common
import (
"strings"
"testing"
"github.com/larksuite/cli/internal/core"
"github.com/spf13/cobra"
)
func resolveOpenIDsTestRuntime(userOpenID string) *RuntimeContext {
cmd := &cobra.Command{Use: "test"}
cfg := &core.CliConfig{UserOpenId: userOpenID}
return TestNewRuntimeContext(cmd, cfg)
}
func TestResolveOpenIDs_Empty(t *testing.T) {
rt := resolveOpenIDsTestRuntime("ou_self")
out, err := ResolveOpenIDs("--user-ids", nil, rt)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(out) != 0 {
t.Fatalf("expected empty, got %v", out)
}
}
func TestResolveOpenIDs_ExpandsMeAndDedups(t *testing.T) {
rt := resolveOpenIDsTestRuntime("ou_self")
out, err := ResolveOpenIDs("--user-ids", []string{"me", "ou_a", "me", "ou_a"}, rt)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := []string{"ou_self", "ou_a"}
if len(out) != len(want) || out[0] != want[0] || out[1] != want[1] {
t.Fatalf("got %v, want %v", out, want)
}
}
func TestResolveOpenIDs_MeIsCaseInsensitive(t *testing.T) {
rt := resolveOpenIDsTestRuntime("ou_self")
out, err := ResolveOpenIDs("--user-ids", []string{"ou_other", "me", "Me", "ME"}, rt)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := []string{"ou_other", "ou_self"}
if len(out) != len(want) || out[0] != want[0] || out[1] != want[1] {
t.Fatalf("got %v, want %v", out, want)
}
}
func TestResolveOpenIDs_MeWithoutLogin(t *testing.T) {
rt := resolveOpenIDsTestRuntime("")
_, err := ResolveOpenIDs("--user-ids", []string{"me"}, rt)
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Error(), "--user-ids") {
t.Fatalf("error should mention the offending flag name; got: %v", err)
}
}
func TestResolveOpenIDs_DedupIsCaseInsensitive(t *testing.T) {
rt := resolveOpenIDsTestRuntime("ou_self")
// Same underlying open_id with three case variants — should collapse to
// one entry, preserving the first-occurrence form.
out, err := ResolveOpenIDs("--user-ids", []string{"ou_abc123", "OU_ABC123", "Ou_Abc123"}, rt)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(out) != 1 || out[0] != "ou_abc123" {
t.Fatalf("case-insensitive dedup failed: got %v, want [ou_abc123]", out)
}
}