// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package doc import ( "encoding/json" "strings" "testing" "github.com/larksuite/cli/internal/cmdutil" "github.com/larksuite/cli/internal/httpmock" ) // TestDocsSearchExecutePassesThroughNotice verifies docs +search preserves notices. func TestDocsSearchExecutePassesThroughNotice(t *testing.T) { const notice = "The query is too long and has been truncated to the first 50 characters for search." f, stdout, _, reg := cmdutil.TestFactory(t, docsTestConfigWithAppID("docs-search-notice")) reg.Register(&httpmock.Stub{ Method: "POST", URL: "/open-apis/search/v2/doc_wiki/search", Body: map[string]interface{}{ "code": 0, "msg": "ok", "data": map[string]interface{}{ "notice": notice, "res_units": []interface{}{}, "total": 0, "has_more": false, "page_token": "", }, }, }) if err := mountAndRunDocs(t, DocsSearch, []string{"+search", "--query", "incident", "--format", "json", "--as", "user"}, f, stdout); err != nil { t.Fatalf("DocsSearch.Execute() error = %v", err) } reg.Verify(t) var env map[string]interface{} if err := json.Unmarshal(stdout.Bytes(), &env); err != nil { t.Fatalf("json.Unmarshal(stdout) error = %v\nstdout=%s", err, stdout.String()) } data, _ := env["data"].(map[string]interface{}) if got, _ := data["notice"].(string); got != notice { t.Fatalf("data.notice = %q, want %q; data=%#v", got, notice, data) } } // TestAddIsoTimeFieldsSupportsJSONNumber verifies JSON numbers get ISO fields. func TestAddIsoTimeFieldsSupportsJSONNumber(t *testing.T) { t.Parallel() items := []interface{}{ map[string]interface{}{ "result_meta": map[string]interface{}{ "update_time": json.Number("1774429274"), }, }, } got := addIsoTimeFields(items) item, _ := got[0].(map[string]interface{}) meta, _ := item["result_meta"].(map[string]interface{}) want := unixTimestampToISO8601("1774429274") if meta["update_time_iso"] != want { t.Fatalf("update_time_iso = %v, want %q", meta["update_time_iso"], want) } } func TestToUnixSeconds(t *testing.T) { t.Parallel() got, err := toUnixSeconds("2026-03-25") if err != nil { t.Fatalf("toUnixSeconds() unexpected error: %v", err) } if got <= 0 { t.Fatalf("toUnixSeconds() = %d, want positive unix timestamp", got) } } func TestToUnixSecondsRejectsInvalidInput(t *testing.T) { t.Parallel() if _, err := toUnixSeconds("not-a-time"); err == nil { t.Fatalf("expected invalid time error, got nil") } } func TestBuildDocsSearchRequestRejectsInvalidTime(t *testing.T) { t.Parallel() _, err := buildDocsSearchRequest( "query", `{"open_time":{"start":"not-a-time"}}`, "", "15", ) if err == nil { t.Fatalf("expected invalid time error, got nil") } if !strings.Contains(err.Error(), "invalid open_time.start") { t.Fatalf("unexpected error: %v", err) } } func TestBuildDocsSearchRequestUsesStartAndEndKeys(t *testing.T) { t.Parallel() req, err := buildDocsSearchRequest( "query", `{"open_time":{"start":"2026-03-25","end":"2026-03-26"}}`, "", "15", ) if err != nil { t.Fatalf("unexpected error: %v", err) } docFilter, ok := req["doc_filter"].(map[string]interface{}) if !ok { t.Fatalf("doc_filter has unexpected type %T", req["doc_filter"]) } openTime, ok := docFilter["open_time"].(map[string]interface{}) if !ok { t.Fatalf("open_time has unexpected type %T", docFilter["open_time"]) } if _, ok := openTime["start"]; !ok { t.Fatalf("expected start in open_time filter, got %#v", openTime) } if _, ok := openTime["end"]; !ok { t.Fatalf("expected end in open_time filter, got %#v", openTime) } if _, ok := openTime["start_time"]; ok { t.Fatalf("did not expect start_time in open_time filter, got %#v", openTime) } if _, ok := openTime["end_time"]; ok { t.Fatalf("did not expect end_time in open_time filter, got %#v", openTime) } } func TestBuildDocsSearchRequestKeepsOnlyDocFilterForFolderTokens(t *testing.T) { t.Parallel() req, err := buildDocsSearchRequest( "query", `{"creator_ids":["ou_123"],"folder_tokens":["fld_123"]}`, "", "15", ) if err != nil { t.Fatalf("unexpected error: %v", err) } docFilter, ok := req["doc_filter"].(map[string]interface{}) if !ok { t.Fatalf("doc_filter has unexpected type %T", req["doc_filter"]) } if _, ok := docFilter["creator_ids"]; !ok { t.Fatalf("expected creator_ids in doc_filter, got %#v", docFilter) } if _, ok := docFilter["folder_tokens"]; !ok { t.Fatalf("expected folder_tokens in doc_filter, got %#v", docFilter) } if _, ok := req["wiki_filter"]; ok { t.Fatalf("did not expect wiki_filter when folder_tokens is set, got %#v", req["wiki_filter"]) } } func TestBuildDocsSearchRequestKeepsOnlyWikiFilterForSpaceIDs(t *testing.T) { t.Parallel() req, err := buildDocsSearchRequest( "query", `{"creator_ids":["ou_123"],"space_ids":["space_123"]}`, "", "15", ) if err != nil { t.Fatalf("unexpected error: %v", err) } wikiFilter, ok := req["wiki_filter"].(map[string]interface{}) if !ok { t.Fatalf("wiki_filter has unexpected type %T", req["wiki_filter"]) } if _, ok := wikiFilter["creator_ids"]; !ok { t.Fatalf("expected creator_ids in wiki_filter, got %#v", wikiFilter) } if _, ok := wikiFilter["space_ids"]; !ok { t.Fatalf("expected space_ids in wiki_filter, got %#v", wikiFilter) } if _, ok := req["doc_filter"]; ok { t.Fatalf("did not expect doc_filter when space_ids is set, got %#v", req["doc_filter"]) } } func TestBuildDocsSearchRequestRejectsMixedFolderTokensAndSpaceIDs(t *testing.T) { t.Parallel() _, err := buildDocsSearchRequest( "query", `{"creator_ids":["ou_123"],"folder_tokens":["fld_123"],"space_ids":["space_123"]}`, "", "15", ) if err == nil { t.Fatalf("expected conflict error, got nil") } if !strings.Contains(err.Error(), "folder_tokens") || !strings.Contains(err.Error(), "space_ids") { t.Fatalf("unexpected error: %v", err) } } func TestBuildDocsSearchRequestStripsOppositeScopedKeys(t *testing.T) { t.Parallel() docReq, err := buildDocsSearchRequest( "query", `{"creator_ids":["ou_123"],"folder_tokens":["fld_123"],"space_ids":[]}`, "", "15", ) if err != nil { t.Fatalf("unexpected doc request error: %v", err) } docFilter, ok := docReq["doc_filter"].(map[string]interface{}) if !ok { t.Fatalf("doc_filter has unexpected type %T", docReq["doc_filter"]) } if _, ok := docFilter["space_ids"]; ok { t.Fatalf("did not expect space_ids in doc_filter, got %#v", docFilter) } wikiReq, err := buildDocsSearchRequest( "query", `{"creator_ids":["ou_123"],"space_ids":["space_123"],"folder_tokens":[]}`, "", "15", ) if err != nil { t.Fatalf("unexpected wiki request error: %v", err) } wikiFilter, ok := wikiReq["wiki_filter"].(map[string]interface{}) if !ok { t.Fatalf("wiki_filter has unexpected type %T", wikiReq["wiki_filter"]) } if _, ok := wikiFilter["folder_tokens"]; ok { t.Fatalf("did not expect folder_tokens in wiki_filter, got %#v", wikiFilter) } }