Skip to content

Commit 22ab9fe

Browse files
Fixes #32374: serve the reindex entity list from the index registry (#32375)
* Fixes #32374: serve the reindex entity list from the index registry The Search Indexing app's entity picker read a hardcoded `items.enum` in the UI-local schema JSON, so it was a per-build snapshot of a runtime fact and had drifted 15 entity types behind the index mapping registry. Selecting everything in the dropdown was therefore not the same as selecting `all`, which the app already expands from the registry. It also could never be right for a distribution that registers extra indexes: IndexMappingLoader merges elasticsearch/indexMapping.json plus elasticsearch/collate/indexMapping.json from the classpath at startup, so no enum baked into the UI bundle can express the reindexable set. Add SearchRepository.getIndexedEntityTypes() as the one definition and have both ReindexingOrchestrator.getAll() and a new GET /v1/search/entityTypes read it, so the picker and `all` cannot drift. The UI drops the enum and fills it in ApplicationsClassBase.importSchema, the single funnel AppDetails and AppInstall share; a failed fetch leaves only "All" selectable plus an error toast rather than breaking the whole form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review: align authz with the page, fix the IT and the picker spec manerow, on #32375: - The endpoint was admin-or-bot while the page that calls it is not. SettingsRouter renders AppDetails for `isAdminUser || hasViewPermissions(APPLICATION)` and AppDetails fetches the schema on every mount, so a viewer holding ViewBasic or ViewAll got a red 403 toast for a request they never triggered. Authorize on APPLICATION/VIEW_BASIC instead, which is exactly the page's gate. - The 403 assertion in SearchResourceIT would have been a 404: authorizeAdminOrBot resolves the subject first, and SubjectCache.getUserContext falls back to Entity.getEntityByName, which throws EntityNotFoundException for a user this suite never creates. It could only pass on suite ordering. Pin the user in @BeforeAll, as AppOperationPermissionsIT already does, and flip the assertion to the case that now matters: an Application viewer gets 200. - SearchIndexApplication.spec.ts would have broken. rc-tree-select filters on the node value (treeNodeFilterProp defaults to 'value'), so typing "Table" leaves `tableColumn` visible too, and rc-tree sets title="Table Column" on it; getByTitle is substring by default, so the locator resolved to 2 nodes and waitFor threw in strict mode. Made it exact. The Playwright run on the first commit was green because the impact map never selected this spec — the Settings/** entry picks no spec that opens an app config form. Added a mapping for the application config forms so a change to the schemas, the loader, or the endpoint that fills them selects it. That also puts this PR in a full run, so the locator fix gets exercised. Copilot: - 'all' is now in the injected enum. The backend sentinel is not an index, so the endpoint does not return it, but `default: ["all"]` has to validate against the enum; TreeSelectWidget already filters it out of the child nodes. Pre-existing inconsistency — the old hardcoded enum omitted it too — fixed while here. It also makes the fetch-failure fallback `['all']` rather than empty. - Reused SEARCH_INDEXING_APPLICATION from constants/explore.constants.ts instead of a second copy. Its own `SortingField` import is type-only, so this pulls nothing new into the bundle graph. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the payLoadSize description, which was a copy of batchSize's payLoadSize is a byte size — ReindexingConfiguration.DEFAULT_PAYLOAD_SIZE is SearchClusterMetrics.DEFAULT_BULK_PAYLOAD_SIZE_BYTES — but its description was a verbatim copy of batchSize's "Maximum number of events entities in a batch (Default 100)", so the rendered app docs showed the wrong units and the wrong default for it. Collate's forked schema happened to carry the correct string; resyncing that doc against this one in openmetadata-collate#6326 would have propagated the error downstream instead. Fixing it here keeps both sides on one description. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stub getIndexedEntityTypes in ReindexingOrchestratorTest setupEntitiesExpandsAllAndCountTotalEntitiesSkipsUnsupportedTypes stubbed searchRepository.getEntityIndexMap(), which setupEntities() no longer calls after getAll() moved to getIndexedEntityTypes(). searchRepository is a Mockito mock, so the new call returned Mockito's default empty Set, setupEntities() expanded "all" to nothing, and the entities assertion failed. Stub the method the code now calls. The IndexMapping mocks the old stub built were only there to shape the map, so the import goes with them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Lower the recorded no-positional-locator count to match the baseline #32576 pruned one om-playwright/no-positional-locator entry out of eslint-suppressions.json (1323 -> 1322) without lowering the number recorded in corpus.test.mjs, so `the suppressions baseline matches its recorded state exactly` fails on main and therefore on every branch merged with it: + actual 'om-playwright/no-positional-locator': 1322 - expected 'om-playwright/no-positional-locator': 1323 Both files here are byte-identical to main, so this is not a violation this branch introduced or fixed — only the bookkeeping the pruning PR left behind. Lowering it is what the assertion message asks for; `yarn lint:playwright` is already clean at 0 errors, so there is nothing to prune. * Do not constrain the entity enum when the list cannot be fetched ApplicationConfiguration validates with @rjsf/validator-ajv8 against the stored appConfiguration, so injecting `enum: ["all"]` on a failed fetch made a saved `entities: ["table", ...]` fail validation — during a transient outage an admin could not save the form at all, including edits to unrelated fields. Return the schema untouched instead: the picker degrades to a plain list, but the stored selection stays visible and editable. The toast still fires. Also add the endpoint's own files to the impact-map entry. It listed resources/apps/** and the searchIndex bundle but not the two files that actually produce the list, so editing /v1/search/entityTypes or getIndexedEntityTypes() would not have selected the picker spec — the exact gap the entry exists to close. Both found by Copilot on a111a47. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4517d2b commit 22ab9fe

13 files changed

Lines changed: 289 additions & 125 deletions

File tree

.github/playwright/impact-map.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,26 @@
363363
"playwright/e2e/Pages/Roles.spec.ts"
364364
]
365365
},
366+
{
367+
"_comment": "The application config forms are built from the UI-local application schemas by ApplicationsClassBase, and the Search Indexing form additionally fills its entity picker from GET /v1/search/entityTypes. The Settings/** entry above selects no spec that actually opens an app config form, so a change to the schemas, the loader, or the endpoint that fills them shipped unrun — the SearchIndexApplication entity-picker locator broke once the server-driven list gained tableColumn and nothing caught it.",
368+
"sources": [
369+
"openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/**",
370+
"openmetadata-ui/src/main/resources/ui/src/pages/AppInstall/**",
371+
"openmetadata-ui/src/main/resources/ui/src/utils/ApplicationSchemas/**",
372+
"openmetadata-ui/src/main/resources/ui/src/jsons/applicationSchemas/**",
373+
"openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/**",
374+
"openmetadata-service/src/main/java/org/openmetadata/service/resources/apps/**",
375+
"openmetadata-service/src/main/java/org/openmetadata/service/resources/search/SearchResource.java",
376+
"openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java"
377+
],
378+
"projects": ["chromium", "Basic"],
379+
"specs": [
380+
"playwright/e2e/Pages/SearchIndexApplication.spec.ts",
381+
"playwright/e2e/Pages/DataInsightReportApplication.spec.ts",
382+
"playwright/e2e/Pages/AppStopRunModal.spec.ts",
383+
"playwright/e2e/Pages/AppRunsHistoryLogs.spec.ts"
384+
]
385+
},
366386
{
367387
"sources": [
368388
"openmetadata-service/src/main/java/org/openmetadata/service/security/**",

openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/SearchResourceIT.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static org.junit.jupiter.api.Assertions.assertThrows;
88
import static org.junit.jupiter.api.Assertions.assertTrue;
99

10+
import com.fasterxml.jackson.core.type.TypeReference;
1011
import com.fasterxml.jackson.databind.JsonNode;
1112
import com.fasterxml.jackson.databind.ObjectMapper;
1213
import java.net.URI;
@@ -19,10 +20,13 @@
1920
import java.util.Locale;
2021
import java.util.concurrent.TimeUnit;
2122
import org.awaitility.Awaitility;
23+
import org.junit.jupiter.api.BeforeAll;
2224
import org.junit.jupiter.api.Test;
2325
import org.junit.jupiter.api.extension.ExtendWith;
2426
import org.junit.jupiter.api.parallel.Execution;
2527
import org.junit.jupiter.api.parallel.ExecutionMode;
28+
import org.openmetadata.it.auth.JwtAuthProvider;
29+
import org.openmetadata.it.factories.UserTestFactory;
2630
import org.openmetadata.it.util.SdkClients;
2731
import org.openmetadata.it.util.TestNamespace;
2832
import org.openmetadata.it.util.TestNamespaceExtension;
@@ -1985,4 +1989,74 @@ void testExportWithFromBeyondResults(TestNamespace ns) throws Exception {
19851989
String[] lines = response.body().split("\n");
19861990
assertEquals(1, lines.length, "Export beyond results should only contain header");
19871991
}
1992+
1993+
// ===================================================================
1994+
// INDEXED ENTITY TYPES (backs the reindex entity picker)
1995+
// ===================================================================
1996+
1997+
@Test
1998+
void testEntityTypesComesFromTheIndexRegistry(TestNamespace ns) throws Exception {
1999+
HttpResponse<String> response = httpGetJson("/v1/search/entityTypes");
2000+
2001+
assertEquals(200, response.statusCode());
2002+
2003+
List<String> entityTypes =
2004+
OBJECT_MAPPER.readValue(response.body(), new TypeReference<List<String>>() {});
2005+
2006+
assertFalse(entityTypes.isEmpty(), "Entity types should not be empty");
2007+
assertTrue(entityTypes.contains("table"), "Entity types should contain table");
2008+
// tableColumn has an index mapping but was absent from the enum the UI used to hardcode.
2009+
// Its presence is what proves the list is read from the registry rather than copied.
2010+
assertTrue(entityTypes.contains("tableColumn"), "Entity types should contain tableColumn");
2011+
assertEquals(
2012+
entityTypes.stream().sorted().toList(), entityTypes, "Entity types should be sorted");
2013+
}
2014+
2015+
/**
2016+
* A dataConsumer JWT hits SubjectCache.getUserContext during authorization; if that user has not
2017+
* been created in this JVM session the lookup throws EntityNotFoundException (→404) and
2018+
* short-circuits the authorizer before it can reach the permission check. Pin the user up front so
2019+
* the result is deterministic regardless of suite ordering.
2020+
*/
2021+
@BeforeAll
2022+
static void ensureDataConsumerUser() {
2023+
UserTestFactory.getDataConsumer(null);
2024+
}
2025+
2026+
@Test
2027+
void testEntityTypesIsReadableByApplicationViewer(TestNamespace ns) throws Exception {
2028+
String consumerToken =
2029+
JwtAuthProvider.tokenFor(
2030+
"data-consumer@open-metadata.org",
2031+
"data-consumer@open-metadata.org",
2032+
new String[] {"DataConsumer"},
2033+
3600);
2034+
2035+
HttpResponse<String> response = httpGetJson("/v1/search/entityTypes", consumerToken);
2036+
2037+
// The app details page renders for anyone with Application view permission and fetches the
2038+
// config schema on mount, so a non-admin viewer must not get a 403 here.
2039+
assertEquals(
2040+
200, response.statusCode(), "Application viewer should be able to list entity types");
2041+
assertFalse(
2042+
OBJECT_MAPPER.readValue(response.body(), new TypeReference<List<String>>() {}).isEmpty(),
2043+
"Entity types should not be empty for an Application viewer");
2044+
}
2045+
2046+
private HttpResponse<String> httpGetJson(String path) throws Exception {
2047+
return httpGetJson(path, SdkClients.getAdminToken());
2048+
}
2049+
2050+
private HttpResponse<String> httpGetJson(String path, String token) throws Exception {
2051+
HttpRequest request =
2052+
HttpRequest.newBuilder()
2053+
.uri(URI.create(SdkClients.getServerUrl() + path))
2054+
.header("Authorization", "Bearer " + token)
2055+
.header("Accept", "application/json")
2056+
.timeout(Duration.ofSeconds(30))
2057+
.GET()
2058+
.build();
2059+
2060+
return HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
2061+
}
19882062
}

openmetadata-service/src/main/java/org/openmetadata/service/apps/bundles/searchIndex/ReindexingOrchestrator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ private void setupEntities() {
509509
}
510510

511511
private Set<String> getAll() {
512-
return new HashSet<>(searchRepository.getEntityIndexMap().keySet());
512+
return new HashSet<>(searchRepository.getIndexedEntityTypes());
513513
}
514514

515515
private boolean hasSlackConfig() {

openmetadata-service/src/main/java/org/openmetadata/service/resources/search/SearchResource.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import es.co.elastic.clients.elasticsearch.core.SearchResponse;
2121
import io.swagger.v3.oas.annotations.Operation;
2222
import io.swagger.v3.oas.annotations.Parameter;
23+
import io.swagger.v3.oas.annotations.media.ArraySchema;
2324
import io.swagger.v3.oas.annotations.media.Content;
2425
import io.swagger.v3.oas.annotations.media.Schema;
2526
import io.swagger.v3.oas.annotations.parameters.RequestBody;
@@ -62,6 +63,7 @@
6263
import org.openmetadata.schema.search.SearchRequest;
6364
import org.openmetadata.schema.type.EntityReference;
6465
import org.openmetadata.schema.type.Include;
66+
import org.openmetadata.schema.type.MetadataOperation;
6567
import org.openmetadata.schema.utils.JsonUtils;
6668
import org.openmetadata.search.IndexMapping;
6769
import org.openmetadata.service.Entity;
@@ -82,6 +84,8 @@
8284
import org.openmetadata.service.search.SearchUtils;
8385
import org.openmetadata.service.search.indexes.SearchIndex;
8486
import org.openmetadata.service.security.Authorizer;
87+
import org.openmetadata.service.security.policyevaluator.OperationContext;
88+
import org.openmetadata.service.security.policyevaluator.ResourceContext;
8589
import org.openmetadata.service.security.policyevaluator.SubjectContext;
8690
import org.openmetadata.service.util.AsyncService;
8791
import org.openmetadata.service.util.AsyncService.DatabaseOperation;
@@ -834,6 +838,40 @@ public Response aggregateSearchRequest(
834838
return searchRepository.aggregate(aggregationRequest);
835839
}
836840

841+
@GET
842+
@Path("/entityTypes")
843+
@Operation(
844+
operationId = "getIndexedEntityTypes",
845+
summary = "List the entity types that have a search index",
846+
description =
847+
"Entity types registered in the index mapping for this deployment, sorted. Includes "
848+
+ "distribution-specific indexes (e.g. Collate-only entity types) because the "
849+
+ "registry is merged from the classpath at startup. This is the same set that "
850+
+ "reindexing expands \"all\" into, so clients can offer an entity picker without "
851+
+ "hardcoding a list that goes stale.",
852+
responses = {
853+
@ApiResponse(
854+
responseCode = "200",
855+
description = "Sorted list of entity types",
856+
content =
857+
@Content(
858+
mediaType = "application/json",
859+
array = @ArraySchema(schema = @Schema(type = "string")))),
860+
@ApiResponse(responseCode = "403", description = "No view permission on Application")
861+
})
862+
public List<String> getIndexedEntityTypes(@Context SecurityContext securityContext) {
863+
// Gate on Application view, not admin: SettingsRouter renders the app details page for
864+
// `isAdminUser || hasViewPermissions(APPLICATION)` and that page fetches the config schema on
865+
// every mount, so an admin-only check here would 403 a legitimate viewer on a request they
866+
// never triggered.
867+
OperationContext operationContext =
868+
new OperationContext(Entity.APPLICATION, MetadataOperation.VIEW_BASIC);
869+
authorizer.authorize(
870+
securityContext, operationContext, new ResourceContext<>(Entity.APPLICATION));
871+
872+
return List.copyOf(searchRepository.getIndexedEntityTypes());
873+
}
874+
837875
@GET
838876
@Path("/entityTypeCounts")
839877
@Operation(

openmetadata-service/src/main/java/org/openmetadata/service/search/SearchRepository.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,20 @@ public IndexMapping getIndexMapping(String entityType) {
984984
return entityIndexMap.get(entityType);
985985
}
986986

987+
/**
988+
* Entity types that have a search index registered for this deployment, sorted. The registry is
989+
* merged from the classpath at startup ({@code elasticsearch/indexMapping.json} plus
990+
* {@code elasticsearch/collate/indexMapping.json} when present), so Collate-only indexes are
991+
* included without the caller knowing which distribution it runs on.
992+
*
993+
* <p>This is the authoritative reindexing target list: {@code SearchIndexingApplication} expands
994+
* {@code "all"} from it and {@code GET /v1/search/entityTypes} serves it to the entity picker, so
995+
* the two cannot drift.
996+
*/
997+
public Set<String> getIndexedEntityTypes() {
998+
return Collections.unmodifiableSet(new TreeSet<>(entityIndexMap.keySet()));
999+
}
1000+
9871001
/**
9881002
* Register a staged index as the live-write target for {@code entityType} while a reindex
9891003
* populates it. Must be paired with {@link #unregisterStagedIndex(String, String)} once the

openmetadata-service/src/test/java/org/openmetadata/service/apps/bundles/searchIndex/ReindexingOrchestratorTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import org.openmetadata.schema.system.Stats;
4444
import org.openmetadata.schema.system.StepStats;
4545
import org.openmetadata.schema.utils.JsonUtils;
46-
import org.openmetadata.search.IndexMapping;
4746
import org.openmetadata.service.Entity;
4847
import org.openmetadata.service.apps.bundles.searchIndex.OrphanedIndexCleaner.CleanupResult;
4948
import org.openmetadata.service.apps.bundles.searchIndex.SearchIndexApp.ReindexingException;
@@ -316,9 +315,7 @@ void setupEntitiesExpandsAllAndCountTotalEntitiesSkipsUnsupportedTypes() throws
316315
String reportType =
317316
org.openmetadata.schema.analytics.ReportData.ReportDataType.ENTITY_REPORT_DATA.value();
318317

319-
when(searchRepository.getEntityIndexMap())
320-
.thenReturn(
321-
Map.of(Entity.TABLE, mock(IndexMapping.class), reportType, mock(IndexMapping.class)));
318+
when(searchRepository.getIndexedEntityTypes()).thenReturn(Set.of(Entity.TABLE, reportType));
322319
when(entityRepository.getDao()).thenReturn(entityDao);
323320
when(entityDao.listCount(any())).thenReturn(7);
324321

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,13 @@ test.describe('Search Index Application', PLAYWRIGHT_BASIC_TEST_TAG_OBJ, () => {
318318
.getByRole('combobox')
319319
.fill('Table');
320320

321-
const tableTitle = page.getByRole('tree').getByTitle('Table');
321+
// Exact: the entity list is server-driven now, and rc-tree-select filters on the node value
322+
// (treeNodeFilterProp defaults to 'value'), so typing "Table" also leaves `tableColumn` —
323+
// rendered as "Table Column" — visible. A substring getByTitle would match both and break
324+
// strict mode.
325+
const tableTitle = page
326+
.getByRole('tree')
327+
.getByTitle('Table', { exact: true });
322328

323329
// Wait for the filtered tree result to render
324330
await tableTitle.waitFor({ state: 'visible' });

openmetadata-ui/src/main/resources/ui/public/locales/en-US/Applications/SearchIndexingApplication.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ $$
1212
$$section
1313
### Payload Size $(id="payLoadSize")
1414
15-
Maximum number of events entities in a batch (Default 100).
15+
Payload size in bytes (Default 104857600).
1616
1717
$$
1818

openmetadata-ui/src/main/resources/ui/src/components/Settings/Applications/AppDetails/ApplicationsClassBase.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,78 @@
1313

1414
import { AppType } from '../../../../generated/entity/applications/app';
1515
import rdfIndexAppSchema from '../../../../jsons/applicationSchemas/RdfIndexApp.json';
16+
import searchIndexingAppSchema from '../../../../jsons/applicationSchemas/SearchIndexingApplication.json';
17+
import { getSearchEntityTypes } from '../../../../rest/searchAPI';
18+
import { showErrorToast } from '../../../../utils/ToastUtils';
1619
import applicationsClassBase from './ApplicationsClassBase';
1720

21+
jest.mock('../../../../rest/searchAPI', () => ({
22+
getSearchEntityTypes: jest.fn().mockResolvedValue([]),
23+
}));
24+
25+
jest.mock('../../../../utils/ToastUtils', () => ({
26+
showErrorToast: jest.fn(),
27+
}));
28+
29+
const mockGetSearchEntityTypes = getSearchEntityTypes as jest.Mock;
30+
31+
// importSchema resolves to `{}` upstream, so name the one shape these tests read rather
32+
// than reaching through it untyped.
33+
type EntitiesEnumSchema = {
34+
properties: { entities: { items: { enum: string[] } } };
35+
};
36+
1837
describe('ApplicationsClassBase', () => {
38+
beforeEach(() => {
39+
jest.clearAllMocks();
40+
mockGetSearchEntityTypes.mockResolvedValue([]);
41+
});
42+
1943
describe('importSchema', () => {
44+
it('should fill the SearchIndexingApplication entity list from the server', async () => {
45+
mockGetSearchEntityTypes.mockResolvedValue(['dynamicAgent', 'table']);
46+
47+
const schema = (await applicationsClassBase.importSchema(
48+
'SearchIndexingApplication'
49+
)) as EntitiesEnumSchema;
50+
51+
// 'all' is the backend sentinel for "every registered index"; it is not an index, so the
52+
// endpoint does not return it, but the ["all"] default has to validate against the enum.
53+
expect(schema.properties.entities.items.enum).toEqual([
54+
'all',
55+
'dynamicAgent',
56+
'table',
57+
]);
58+
});
59+
60+
it('should not hardcode the entity list in the schema json', () => {
61+
const { items } = searchIndexingAppSchema.properties.entities;
62+
63+
// A shipped enum is the bug this endpoint replaced: one list for every deployment,
64+
// stale every time an entity type was added and blind to Collate-only indexes.
65+
expect(items).not.toHaveProperty('enum');
66+
});
67+
68+
it('should toast and leave the schema unconstrained when the server call fails', async () => {
69+
const error = new Error('boom');
70+
mockGetSearchEntityTypes.mockRejectedValue(error);
71+
72+
const schema = (await applicationsClassBase.importSchema(
73+
'SearchIndexingApplication'
74+
)) as EntitiesEnumSchema;
75+
76+
// Narrowing the enum here would make a stored `entities: ["table", …]` fail the
77+
// form's AJV validation and block saving until the endpoint recovers.
78+
expect(schema.properties.entities.items).not.toHaveProperty('enum');
79+
expect(showErrorToast).toHaveBeenCalledWith(error);
80+
});
81+
82+
it('should not fetch entity types for other applications', async () => {
83+
await applicationsClassBase.importSchema('RdfIndexApp');
84+
85+
expect(mockGetSearchEntityTypes).not.toHaveBeenCalled();
86+
});
87+
2088
it('should import pre-parsed schema', async () => {
2189
// Mock the dynamic import
2290
jest.doMock(

0 commit comments

Comments
 (0)