| author | |
| committer | |
| log | 421088c27367da227d922ce999c1637416a6d0f4 |
| tree | fcbee70081971742e0dfc894cd5925321fd5bdb0 |
| parent | 592c5983c14d248aa3cfddb8e6c7372f12cd1ab6 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Simone <185146821+Lucenx9@users.noreply.github.com>2 files changed, 59 insertions(+), 10 deletions(-)
packages/client-runtime/src/state/threadSearch.test.ts+45| ... | ... | @@ -49,6 +49,51 @@ it("encodes scoped thread keys without delimiter collisions", () => { |
| 49 | 49 | expect(first).not.toBe(second); |
| 50 | 50 | }); |
| 51 | 51 | |
| 52 | it("accepts search keys at the maximum decoded query length", () => { | |
| 53 | const queries: string[] = []; | |
| 54 | const searchAtom = createThreadSearchResultsAtomFamily<Error>({ | |
| 55 | getSearchAtom: (_environmentId, query) => { | |
| 56 | queries.push(query); | |
| 57 | return Atom.make(AsyncResult.success({ matches: [] })); | |
| 58 | }, | |
| 59 | labelPrefix: "test:thread-search", | |
| 60 | }); | |
| 61 | const registry = AtomRegistry.make(); | |
| 62 | const query = "a".repeat(200); | |
| 63 | ||
| 64 | try { | |
| 65 | registry.get(searchAtom(makeThreadSearchKey([envA], query))); | |
| 66 | registry.get(searchAtom(makeThreadSearchKey([envA], ` ${query} `))); | |
| 67 | ||
| 68 | expect(queries).toEqual([query, query]); | |
| 69 | } finally { | |
| 70 | registry.dispose(); | |
| 71 | } | |
| 72 | }); | |
| 73 | ||
| 74 | it("ignores invalid search keys", () => { | |
| 75 | let searchCount = 0; | |
| 76 | const searchAtom = createThreadSearchResultsAtomFamily<Error>({ | |
| 77 | getSearchAtom: () => { | |
| 78 | searchCount += 1; | |
| 79 | return Atom.make(AsyncResult.success({ matches: [] })); | |
| 80 | }, | |
| 81 | labelPrefix: "test:thread-search", | |
| 82 | }); | |
| 83 | const registry = AtomRegistry.make(); | |
| 84 | ||
| 85 | try { | |
| 86 | const state = registry.get(searchAtom(makeThreadSearchKey([envA], "a".repeat(201)))); | |
| 87 | const malformedState = registry.get(searchAtom("not json{{")); | |
| 88 | ||
| 89 | expect(state).toEqual({ matches: [], isLoading: false }); | |
| 90 | expect(malformedState).toEqual({ matches: [], isLoading: false }); | |
| 91 | expect(searchCount).toBe(0); | |
| 92 | } finally { | |
| 93 | registry.dispose(); | |
| 94 | } | |
| 95 | }); | |
| 96 | ||
| 52 | 97 | it("merges successful environments and silently ignores failures", () => { |
| 53 | 98 | const result: OrchestrationSearchThreadsResult = { |
| 54 | 99 | matches: [ |
packages/client-runtime/src/state/threadSearch.ts+14-10| ... | ... | @@ -17,11 +17,10 @@ export interface ThreadSearchResultsState { |
| 17 | 17 | readonly isLoading: boolean; |
| 18 | 18 | } |
| 19 | 19 | |
| 20 | const ThreadSearchKey = Schema.Tuple([ | |
| 21 | Schema.Array(EnvironmentId), | |
| 22 | OrchestrationSearchThreadsInput.fields.query, | |
| 23 | ]); | |
| 24 | const decodeThreadSearchKey = Schema.decodeUnknownSync(ThreadSearchKey); | |
| 20 | const ThreadSearchKey = Schema.fromJsonString( | |
| 21 | Schema.Tuple([Schema.Array(EnvironmentId), OrchestrationSearchThreadsInput.fields.query]), | |
| 22 | ); | |
| 23 | const decodeThreadSearchKey = Schema.decodeUnknownOption(ThreadSearchKey); | |
| 25 | 24 | |
| 26 | 25 | export function makeThreadSearchKey( |
| 27 | 26 | environmentIds: ReadonlyArray<EnvironmentId>, |
| ... | ... | @@ -34,7 +33,7 @@ export function makeThreadSearchKey( |
| 34 | 33 | } |
| 35 | 34 | |
| 36 | 35 | function parseThreadSearchKey(key: string) { |
| 37 | return decodeThreadSearchKey(JSON.parse(key)); | |
| 36 | return decodeThreadSearchKey(key); | |
| 38 | 37 | } |
| 39 | 38 | |
| 40 | 39 | export function threadSearchMatchKey( |
| ... | ... | @@ -44,9 +43,9 @@ export function threadSearchMatchKey( |
| 44 | 43 | } |
| 45 | 44 | |
| 46 | 45 | /** |
| 47 | * Combines one search query atom per environment. Failed and disconnected | |
| 48 | * environments contribute no content matches, preserving local title search | |
| 49 | * as the compatibility fallback. | |
| 46 | * Combines one search query atom per environment. Invalid search keys, failed | |
| 47 | * requests, and disconnected environments contribute no content matches, | |
| 48 | * preserving local title search as the compatibility fallback. | |
| 50 | 49 | */ |
| 51 | 50 | export function createThreadSearchResultsAtomFamily<E>(options: { |
| 52 | 51 | readonly getSearchAtom: ( |
| ... | ... | @@ -57,7 +56,12 @@ export function createThreadSearchResultsAtomFamily<E>(options: { |
| 57 | 56 | }) { |
| 58 | 57 | return Atom.family((key: string) => |
| 59 | 58 | Atom.make((get): ThreadSearchResultsState => { |
| 60 | const [environmentIds, query] = parseThreadSearchKey(key); | |
| 59 | const parsedKey = parseThreadSearchKey(key); | |
| 60 | if (Option.isNone(parsedKey)) { | |
| 61 | return { matches: [], isLoading: false }; | |
| 62 | } | |
| 63 | ||
| 64 | const [environmentIds, query] = parsedKey.value; | |
| 61 | 65 | const matches: EnvironmentThreadSearchMatch[] = []; |
| 62 | 66 | let isLoading = false; |
| 63 | 67 |