search testing
The helpers this package ships for your tests, by the path you import them from. How the test layers fit together is in the testing guide.
@evolve-framework/search/testing
InMemoryIndexAdapter and FakeCatalogSource, for testing an indexer or a mapper without a provider, and describeSearchIndexAdapterContract, the scenarios every index adapter meets
FakeCatalogSourceA DocumentSource over an in-memory list of entities, so the sync engine can be tested without a commerce backend.
import { FakeCatalogSource } from "@evolve-framework/search/testing";new FakeCatalogSource(options: FakeCatalogSourceOptions)| Option | Type | Description |
|---|---|---|
scopes | IndexScope[] | |
entities | FakeEntity[] | |
fansOut? | boolean | Fan each entity out into one document per SKU. |
affects? | Record<string, string[]> | Entities whose documents embed each other, keyed by entity id. |
documentTypes? | string[] | Types this source declares. Defaults to the distinct types its entities emit, which is what a real source derives from its mappers. |
| Method | Description |
|---|---|
getAffectedEntities(sourceId: string): Promise<string[]> | Other entities whose documents embed data from this one, and are therefore stale once it changes. |
getScopes(): Promise<IndexScope[]> | Every scope this source feeds. |
getDocuments(
scope: IndexScope,
sourceId: string,
): Promise<ProductDocument[]> | Build every document for one source entity in one scope. |
iterate(scope: IndexScope): AsyncIterable<ProductDocument[]> | Every document for a scope, batched per source entity. |
InMemoryIndexAdapterA SearchIndexAdapter that keeps documents in a Map, for testing the sync engine and any DocumentSource without a provider.
import { InMemoryIndexAdapter } from "@evolve-framework/search/testing";Records the batch sizes it was called with, so tests can assert that chunking
respects batchSize.
new InMemoryIndexAdapter(options?: InMemoryIndexAdapterOptions)| Option | Type | Description |
|---|---|---|
batchSize? | number | Maximum documents per upsert call. Defaults to 100. |
deleteBySource? | boolean | Advertise and implement |
fullReindex? | boolean | Advertise and implement |
| Method | Description |
|---|---|
init(): Promise<void> | One-shot, idempotent initialization (build SDK clients, etc.). |
upsertDocuments(scope: IndexScope, documents: TDoc[]): Promise<void> | Store each document under its id in the scope's target for its type, replacing one already there, and record the batch size in upsertBatches. |
deleteDocuments(
scope: IndexScope,
ids: string[],
documentTypes: string[],
): Promise<void> | Remove documents by id, from the target of every type in documentTypes. |
documents(scope: IndexScope, documentType?: string | undefined): TDoc[] | Every document currently in a scope's target. |
documentIds(
scope: IndexScope,
documentType?: string,
): string[] | The ids of InMemoryIndexAdapter.documents, in the same order. |
describeSearchIndexAdapterContractThe scenarios every SearchIndexAdapter meets, run the way the framework drives an adapter: through IndexSyncService over a FakeCatalogSource.
import { describeSearchIndexAdapterContract } from "@evolve-framework/search/testing";Scenarios a capability rules out are skipped rather than failed.
function describeSearchIndexAdapterContract( name: string, create: () => IndexAdapterUnderTest | Promise<IndexAdapterUnderTest>,): voidReturns void
FakeEntity
import { type FakeEntity } from "@evolve-framework/search/testing";export type FakeEntity = { id: string; name: string; skus?: string[]; storeKeys?: string[]; documentType?: string;};IndexAdapterUnderTestOne adapter over a fresh, empty backend, and the means to look inside it.
import { type IndexAdapterUnderTest } from "@evolve-framework/search/testing";export type IndexAdapterUnderTest = { adapter: SearchIndexAdapter<ProductDocument>; read: (scope: IndexScope, documentType: string) => Promise<string[]>; failNextWrite?: () => void;};
