@webiny/api-headless-cms-testing 0.0.0-unstable.0156a4c312
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/createCmsTestHandler.d.ts +70 -0
- package/createCmsTestHandler.js +172 -0
- package/createCmsTestHandler.js.map +1 -0
- package/index.d.ts +7 -0
- package/index.js +3 -0
- package/package.json +39 -0
- package/processLegacyPlugins.d.ts +12 -0
- package/processLegacyPlugins.js +12 -0
- package/processLegacyPlugins.js.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Webiny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# @webiny/api-headless-cms-testing
|
|
2
|
+
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This package is part of the [Webiny](https://www.webiny.com) monorepo.
|
|
5
|
+
> Itβs **included in every Webiny project by default** and is not meant to be used as a standalone package.
|
|
6
|
+
|
|
7
|
+
π **Documentation:** [https://www.webiny.com/docs](https://www.webiny.com/docs)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
_This README file is automatically generated during the publish process._
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Container } from "@webiny/di";
|
|
2
|
+
import type { IdentityData } from "@webiny/api-core/features/security/IdentityContext/index.js";
|
|
3
|
+
import type { SecurityPermission } from "@webiny/api-core/types/security.js";
|
|
4
|
+
import type { DecryptedWcpProjectLicense } from "@webiny/wcp/types.js";
|
|
5
|
+
export interface CmsTestHandlerParams {
|
|
6
|
+
/** Identity for the request. `undefined` β a default admin; `null` β anonymous. */
|
|
7
|
+
identity?: IdentityData | null;
|
|
8
|
+
permissions?: SecurityPermission[];
|
|
9
|
+
/**
|
|
10
|
+
* Permissions of other identities, by identity id, for code that evaluates another user
|
|
11
|
+
* through `IdentityContext.withIdentity`. The request identity gets `permissions`; other
|
|
12
|
+
* identities not listed here get none.
|
|
13
|
+
*/
|
|
14
|
+
permissionsByIdentity?: Record<string, SecurityPermission[]>;
|
|
15
|
+
testProjectLicense?: DecryptedWcpProjectLicense;
|
|
16
|
+
/** CMS endpoint type to build. Defaults to "manage". */
|
|
17
|
+
cmsType?: "manage" | "read" | "preview";
|
|
18
|
+
/** Plugins forwarded to `HeadlessCmsFeature.register` as `extraPlugins` (e.g. CMS model plugins). */
|
|
19
|
+
extraCmsPlugins?: any[];
|
|
20
|
+
/**
|
|
21
|
+
* Legacy escape hatch (being retired β prefer `setup`). A mixed array, dispatched like the old
|
|
22
|
+
* `useContextHandler`:
|
|
23
|
+
* - RegisterExtensionPlugins are applied at register() time (DI registration);
|
|
24
|
+
* - static plugins (CmsModelPlugin, GraphQLSchemaPlugin, β¦) are forwarded to HeadlessCmsFeature
|
|
25
|
+
* as extraPlugins so their models reach the container before any initializer caches them;
|
|
26
|
+
* - plain `container => {}` functions run LAST (after `setup`), so they can override defaults.
|
|
27
|
+
*/
|
|
28
|
+
legacyPlugins?: any;
|
|
29
|
+
/**
|
|
30
|
+
* Register the consuming package's own features here (e.g. AcoFeature, FileModel, plus its own
|
|
31
|
+
* getStorageOps presets via processLegacyPlugins). Runs in the request phase AFTER ApiCore + CMS
|
|
32
|
+
* storage + HeadlessCmsFeature, and BEFORE the GraphQL engine β i.e. before any initializer or
|
|
33
|
+
* resolver, the same window the real app handler uses.
|
|
34
|
+
*/
|
|
35
|
+
setup?: (container: Container) => void | Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export interface InvokeParams {
|
|
38
|
+
httpMethod?: "POST";
|
|
39
|
+
type?: string;
|
|
40
|
+
body: {
|
|
41
|
+
query: string;
|
|
42
|
+
variables?: Record<string, any>;
|
|
43
|
+
};
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Shared integration test handler that wires the real CMS base (ApiCore + HeadlessCms + GraphQL
|
|
48
|
+
* engine + the CMS manage route) the same way the AWS app handler does β auth via Test
|
|
49
|
+
* authenticator/authorizer, root tenant seeded, storage via getStorageOps. Consuming packages add
|
|
50
|
+
* their own features through `params.setup` and build their SDKs on top of `invoke`/`invokeCms`,
|
|
51
|
+
* or grab the fully-initialized request context via `getContext()`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const createCmsTestHandler: (params?: CmsTestHandlerParams) => {
|
|
54
|
+
handler: (request: Partial<import("@webiny/event-handler-core/index.js").IHttpRequest> & {
|
|
55
|
+
method: string;
|
|
56
|
+
path: string;
|
|
57
|
+
}) => Promise<import("@webiny/event-handler-core/index.js").IHttpResponse>;
|
|
58
|
+
invoke: ({ httpMethod, body, headers }: InvokeParams) => Promise<readonly [any, import("@webiny/event-handler-core/index.js").IHttpResponse]>;
|
|
59
|
+
invokeCms: ({ httpMethod, type, body, headers }: InvokeParams) => Promise<readonly [any, import("@webiny/event-handler-core/index.js").IHttpResponse]>;
|
|
60
|
+
getContext: <C extends Record<string, any> = Record<string, any>>() => Promise<C>;
|
|
61
|
+
createQuery: <T extends Record<string, any> = Record<string, any>>(query: string) => (variables?: Record<string, any>, headers?: Record<string, string>) => Promise<readonly [T, any]>;
|
|
62
|
+
createMutation: <T extends Record<string, any> = Record<string, any>>(mutation: string) => (variables?: Record<string, any>, headers?: Record<string, string>) => Promise<readonly [T, any]>;
|
|
63
|
+
introspect: () => Promise<readonly [any, import("@webiny/event-handler-core/index.js").IHttpResponse]>;
|
|
64
|
+
identity: IdentityData | null;
|
|
65
|
+
tenant: {
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
parent: null;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createTestHttpHandler } from "@webiny/event-handler-core/features/testing";
|
|
2
|
+
import { ApiCoreFeature, registerApiCoreStorageOperations } from "@webiny/api-core";
|
|
3
|
+
import { GraphQLEngineFeature, GraphQLSchemaCacheFeature, GraphQLSchemaKeyVerificationFeature } from "@webiny/api-graphql";
|
|
4
|
+
import { EncryptionKeyCacheFeature } from "@webiny/api-core/features/encryption/index.js";
|
|
5
|
+
import { JwksStoreFeature } from "@webiny/api-core/idp/index.js";
|
|
6
|
+
import { WcpLicenseLoader } from "@webiny/api-core/features/wcp/WcpLicenseLoader.js";
|
|
7
|
+
import { getStorageOps } from "@webiny/api-core/testing/environment.js";
|
|
8
|
+
import { createTestWcpLicense } from "@webiny/wcp/testing/createTestWcpLicense.js";
|
|
9
|
+
import { getIntrospectionQuery } from "graphql";
|
|
10
|
+
import { HeadlessCmsFeature } from "@webiny/api-headless-cms/HeadlessCmsFeature.js";
|
|
11
|
+
import { AuthTriggerHandler, RootTenantInitializer, TestAuthenticator, TestAuthorizer, TestIdentity, TestPermissions } from "@webiny/api-core-testing";
|
|
12
|
+
import { processLegacyPlugins } from "./processLegacyPlugins.js";
|
|
13
|
+
import { Logger } from "@webiny/api-core/features/logger/index.js";
|
|
14
|
+
import { createLogger } from "@webiny/logger";
|
|
15
|
+
const DEFAULT_IDENTITY = {
|
|
16
|
+
id: "id-12345678",
|
|
17
|
+
type: "admin",
|
|
18
|
+
displayName: "John Doe"
|
|
19
|
+
};
|
|
20
|
+
const createCmsTestHandler = (params = {})=>{
|
|
21
|
+
const apiCoreStorage = getStorageOps("apiCore");
|
|
22
|
+
const cmsStorage = getStorageOps("cms");
|
|
23
|
+
const identity = void 0 === params.identity ? DEFAULT_IDENTITY : params.identity;
|
|
24
|
+
const permissions = void 0 === params.permissions ? [
|
|
25
|
+
{
|
|
26
|
+
name: "*"
|
|
27
|
+
}
|
|
28
|
+
] : params.permissions;
|
|
29
|
+
const cmsType = params.cmsType ?? "manage";
|
|
30
|
+
const setupRoot = (container)=>{
|
|
31
|
+
container.registerInstance(TestIdentity, identity);
|
|
32
|
+
container.registerInstance(TestPermissions, {
|
|
33
|
+
list: permissions,
|
|
34
|
+
byIdentity: params.permissionsByIdentity
|
|
35
|
+
});
|
|
36
|
+
container.register(TestAuthenticator);
|
|
37
|
+
container.register(TestAuthorizer);
|
|
38
|
+
container.registerDecorator(AuthTriggerHandler);
|
|
39
|
+
container.registerDecorator(RootTenantInitializer);
|
|
40
|
+
GraphQLSchemaCacheFeature.register(container);
|
|
41
|
+
GraphQLSchemaKeyVerificationFeature.register(container);
|
|
42
|
+
EncryptionKeyCacheFeature.register(container);
|
|
43
|
+
JwksStoreFeature.register(container);
|
|
44
|
+
};
|
|
45
|
+
const setupRequest = async (container)=>{
|
|
46
|
+
const wcpLicense = await WcpLicenseLoader.load(params.testProjectLicense ?? createTestWcpLicense());
|
|
47
|
+
registerApiCoreStorageOperations(container, apiCoreStorage.storageOperations);
|
|
48
|
+
const logger = createLogger();
|
|
49
|
+
container.registerInstance(Logger, logger);
|
|
50
|
+
ApiCoreFeature.register(container, {
|
|
51
|
+
wcpLicense
|
|
52
|
+
});
|
|
53
|
+
processLegacyPlugins(container, cmsStorage.plugins);
|
|
54
|
+
const legacyItems = [
|
|
55
|
+
params.legacyPlugins
|
|
56
|
+
].flat(1 / 0).filter(Boolean);
|
|
57
|
+
const isFn = (p)=>"function" == typeof p && !p.prototype;
|
|
58
|
+
const legacyObjects = legacyItems.filter((p)=>!isFn(p));
|
|
59
|
+
processLegacyPlugins(container, legacyObjects);
|
|
60
|
+
const staticPlugins = legacyObjects.filter((p)=>"function" != typeof p.apply && "function" != typeof p);
|
|
61
|
+
HeadlessCmsFeature.register(container, {
|
|
62
|
+
type: cmsType,
|
|
63
|
+
extraPlugins: [
|
|
64
|
+
...params.extraCmsPlugins ?? [],
|
|
65
|
+
...staticPlugins
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
await params.setup?.(container);
|
|
69
|
+
for (const plugin of legacyItems.filter(isFn))plugin(container);
|
|
70
|
+
};
|
|
71
|
+
const handler = createTestHttpHandler({
|
|
72
|
+
root: setupRoot,
|
|
73
|
+
child: async (container)=>{
|
|
74
|
+
await setupRequest(container);
|
|
75
|
+
GraphQLEngineFeature.register(container);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const invoke = async ({ httpMethod = "POST", body, headers = {} })=>{
|
|
79
|
+
const response = await handler({
|
|
80
|
+
method: httpMethod,
|
|
81
|
+
path: "/graphql",
|
|
82
|
+
headers: {
|
|
83
|
+
"x-tenant": "root",
|
|
84
|
+
"content-type": "application/json",
|
|
85
|
+
...headers
|
|
86
|
+
},
|
|
87
|
+
body
|
|
88
|
+
});
|
|
89
|
+
return [
|
|
90
|
+
response.body,
|
|
91
|
+
response
|
|
92
|
+
];
|
|
93
|
+
};
|
|
94
|
+
const invokeCms = async ({ httpMethod = "POST", type = "manage", body, headers = {} })=>{
|
|
95
|
+
const response = await handler({
|
|
96
|
+
method: httpMethod,
|
|
97
|
+
path: `/cms/${type}`,
|
|
98
|
+
headers: {
|
|
99
|
+
"x-tenant": "root",
|
|
100
|
+
"content-type": "application/json",
|
|
101
|
+
...headers
|
|
102
|
+
},
|
|
103
|
+
body
|
|
104
|
+
});
|
|
105
|
+
return [
|
|
106
|
+
response.body,
|
|
107
|
+
response
|
|
108
|
+
];
|
|
109
|
+
};
|
|
110
|
+
const getContext = async ()=>{
|
|
111
|
+
let requestContainer;
|
|
112
|
+
const ctxHandler = createTestHttpHandler({
|
|
113
|
+
root: setupRoot,
|
|
114
|
+
child: async (container)=>{
|
|
115
|
+
await setupRequest(container);
|
|
116
|
+
GraphQLEngineFeature.register(container);
|
|
117
|
+
requestContainer = container;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
await ctxHandler({
|
|
121
|
+
method: "POST",
|
|
122
|
+
path: "/graphql",
|
|
123
|
+
headers: {
|
|
124
|
+
"x-tenant": "root",
|
|
125
|
+
"content-type": "application/json"
|
|
126
|
+
},
|
|
127
|
+
body: {
|
|
128
|
+
query: "{ __typename }"
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
container: requestContainer
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
const createQuery = (query)=>(variables, headers = {})=>invoke({
|
|
136
|
+
body: {
|
|
137
|
+
query,
|
|
138
|
+
variables: variables || void 0
|
|
139
|
+
},
|
|
140
|
+
headers
|
|
141
|
+
});
|
|
142
|
+
const createMutation = (mutation)=>(variables, headers = {})=>invoke({
|
|
143
|
+
body: {
|
|
144
|
+
query: mutation,
|
|
145
|
+
variables: variables || void 0
|
|
146
|
+
},
|
|
147
|
+
headers
|
|
148
|
+
});
|
|
149
|
+
const introspect = ()=>invoke({
|
|
150
|
+
body: {
|
|
151
|
+
query: getIntrospectionQuery()
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
handler,
|
|
156
|
+
invoke,
|
|
157
|
+
invokeCms,
|
|
158
|
+
getContext,
|
|
159
|
+
createQuery,
|
|
160
|
+
createMutation,
|
|
161
|
+
introspect,
|
|
162
|
+
identity,
|
|
163
|
+
tenant: {
|
|
164
|
+
id: "root",
|
|
165
|
+
name: "Root",
|
|
166
|
+
parent: null
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
export { createCmsTestHandler };
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=createCmsTestHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCmsTestHandler.js","sources":["../src/createCmsTestHandler.ts"],"sourcesContent":["import { createTestHttpHandler } from \"@webiny/event-handler-core/features/testing\";\nimport type { Container } from \"@webiny/di\";\nimport { ApiCoreFeature, registerApiCoreStorageOperations } from \"@webiny/api-core\";\nimport { GraphQLSchemaCacheFeature } from \"@webiny/api-graphql\";\nimport { GraphQLSchemaKeyVerificationFeature } from \"@webiny/api-graphql\";\nimport { EncryptionKeyCacheFeature } from \"@webiny/api-core/features/encryption/index.js\";\nimport { JwksStoreFeature } from \"@webiny/api-core/idp/index.js\";\nimport { GraphQLEngineFeature } from \"@webiny/api-graphql\";\nimport { WcpLicenseLoader } from \"@webiny/api-core/features/wcp/WcpLicenseLoader.js\";\nimport { getStorageOps } from \"@webiny/api-core/testing/environment.js\";\nimport { createTestWcpLicense } from \"@webiny/wcp/testing/createTestWcpLicense.js\";\nimport { getIntrospectionQuery } from \"graphql\";\nimport type { ApiCoreStorageOperations } from \"@webiny/api-core/types/core.js\";\nimport type { IdentityData } from \"@webiny/api-core/features/security/IdentityContext/index.js\";\nimport type { SecurityPermission } from \"@webiny/api-core/types/security.js\";\nimport type { DecryptedWcpProjectLicense } from \"@webiny/wcp/types.js\";\nimport { HeadlessCmsFeature } from \"@webiny/api-headless-cms/HeadlessCmsFeature.js\";\nimport { TestIdentity, TestAuthenticator } from \"@webiny/api-core-testing\";\nimport { TestPermissions, TestAuthorizer } from \"@webiny/api-core-testing\";\nimport { AuthTriggerHandler } from \"@webiny/api-core-testing\";\nimport { RootTenantInitializer } from \"@webiny/api-core-testing\";\nimport { processLegacyPlugins } from \"./processLegacyPlugins.js\";\nimport { Logger } from \"@webiny/api-core/features/logger/index.js\";\nimport { createLogger } from \"@webiny/logger\";\n\nconst DEFAULT_IDENTITY: IdentityData = {\n id: \"id-12345678\",\n type: \"admin\",\n displayName: \"John Doe\"\n};\n\nexport interface CmsTestHandlerParams {\n /** Identity for the request. `undefined` β a default admin; `null` β anonymous. */\n identity?: IdentityData | null;\n permissions?: SecurityPermission[];\n /**\n * Permissions of other identities, by identity id, for code that evaluates another user\n * through `IdentityContext.withIdentity`. The request identity gets `permissions`; other\n * identities not listed here get none.\n */\n permissionsByIdentity?: Record<string, SecurityPermission[]>;\n testProjectLicense?: DecryptedWcpProjectLicense;\n /** CMS endpoint type to build. Defaults to \"manage\". */\n cmsType?: \"manage\" | \"read\" | \"preview\";\n /** Plugins forwarded to `HeadlessCmsFeature.register` as `extraPlugins` (e.g. CMS model plugins). */\n extraCmsPlugins?: any[];\n /**\n * Legacy escape hatch (being retired β prefer `setup`). A mixed array, dispatched like the old\n * `useContextHandler`:\n * - RegisterExtensionPlugins are applied at register() time (DI registration);\n * - static plugins (CmsModelPlugin, GraphQLSchemaPlugin, β¦) are forwarded to HeadlessCmsFeature\n * as extraPlugins so their models reach the container before any initializer caches them;\n * - plain `container => {}` functions run LAST (after `setup`), so they can override defaults.\n */\n legacyPlugins?: any;\n /**\n * Register the consuming package's own features here (e.g. AcoFeature, FileModel, plus its own\n * getStorageOps presets via processLegacyPlugins). Runs in the request phase AFTER ApiCore + CMS\n * storage + HeadlessCmsFeature, and BEFORE the GraphQL engine β i.e. before any initializer or\n * resolver, the same window the real app handler uses.\n */\n setup?: (container: Container) => void | Promise<void>;\n}\n\nexport interface InvokeParams {\n httpMethod?: \"POST\";\n type?: string;\n body: { query: string; variables?: Record<string, any> };\n headers?: Record<string, string>;\n}\n\n/**\n * Shared integration test handler that wires the real CMS base (ApiCore + HeadlessCms + GraphQL\n * engine + the CMS manage route) the same way the AWS app handler does β auth via Test\n * authenticator/authorizer, root tenant seeded, storage via getStorageOps. Consuming packages add\n * their own features through `params.setup` and build their SDKs on top of `invoke`/`invokeCms`,\n * or grab the fully-initialized request context via `getContext()`.\n */\nexport const createCmsTestHandler = (params: CmsTestHandlerParams = {}) => {\n const apiCoreStorage = getStorageOps<ApiCoreStorageOperations>(\"apiCore\");\n const cmsStorage = getStorageOps(\"cms\");\n\n const identity = params.identity === undefined ? DEFAULT_IDENTITY : params.identity;\n const permissions = params.permissions === undefined ? [{ name: \"*\" }] : params.permissions;\n const cmsType = params.cmsType ?? \"manage\";\n\n const setupRoot = (container: Container) => {\n container.registerInstance(TestIdentity, identity);\n container.registerInstance(TestPermissions, {\n list: permissions,\n byIdentity: params.permissionsByIdentity\n });\n container.register(TestAuthenticator);\n container.register(TestAuthorizer);\n container.registerDecorator(AuthTriggerHandler);\n container.registerDecorator(RootTenantInitializer);\n // Same as the real handlers, so every request after the first reuses the built schema.\n GraphQLSchemaCacheFeature.register(container);\n // Fails a test when a schema factory's output changes under the same schema key.\n GraphQLSchemaKeyVerificationFeature.register(container);\n // Same as the real handlers, so the encryption key is derived once, not per request.\n EncryptionKeyCacheFeature.register(container);\n // Root, so identity providers' signing keys are fetched once per process, not per request.\n JwksStoreFeature.register(container);\n };\n\n // Everything up to (but not including) the GraphQL engine β shared by the HTTP handler and the\n // context-capture handler.\n const setupRequest = async (container: Container) => {\n const wcpLicense = await WcpLicenseLoader.load(\n params.testProjectLicense ?? createTestWcpLicense()\n );\n\n registerApiCoreStorageOperations(container, apiCoreStorage.storageOperations);\n const logger = createLogger();\n container.registerInstance(Logger, logger);\n ApiCoreFeature.register(container, { wcpLicense });\n\n // CMS storage preset. All storage presets are RegisterExtensionPlugins (registerDynamoDBCore,\n // registerCmsOpenSearchStorageOperations, dbPlugins, ...); processLegacyPlugins runs them at\n // register() time β before HeadlessCmsFeature builds storage, mirroring the real app's\n // registerExtensions.\n processLegacyPlugins(container, cmsStorage.plugins);\n\n // `legacyPlugins` is a mixed bag: plain `container => {}` functions (called last, after\n // `setup`) plus legacy plugin objects β RegisterExtensionPlugins (register-time) or static\n // Plugins (extraPlugins).\n const legacyItems = [params.legacyPlugins].flat(Infinity as 1).filter(Boolean);\n const isFn = (p: any) => typeof p === \"function\" && !p.prototype;\n\n const legacyObjects = legacyItems.filter(p => !isFn(p));\n processLegacyPlugins(container, legacyObjects);\n const staticPlugins = legacyObjects.filter(\n p => typeof (p as any).apply !== \"function\" && typeof p !== \"function\"\n );\n HeadlessCmsFeature.register(container, {\n type: cmsType,\n extraPlugins: [...(params.extraCmsPlugins ?? []), ...staticPlugins]\n });\n\n await params.setup?.(container);\n\n // Function plugins in `legacyPlugins` run LAST β after the consuming package's own `setup` β\n // so they can override defaults it registered (last-wins).\n for (const plugin of legacyItems.filter(isFn)) {\n (plugin as (container: any) => void)(container);\n }\n };\n\n const handler = createTestHttpHandler({\n root: setupRoot,\n child: async container => {\n await setupRequest(container);\n GraphQLEngineFeature.register(container);\n }\n });\n\n const invoke = async ({ httpMethod = \"POST\", body, headers = {} }: InvokeParams) => {\n const response = await handler({\n method: httpMethod,\n path: \"/graphql\",\n headers: { \"x-tenant\": \"root\", \"content-type\": \"application/json\", ...headers },\n body\n });\n return [response.body, response] as const;\n };\n\n const invokeCms = async ({\n httpMethod = \"POST\",\n type = \"manage\",\n body,\n headers = {}\n }: InvokeParams) => {\n const response = await handler({\n method: httpMethod,\n path: `/cms/${type}`,\n headers: { \"x-tenant\": \"root\", \"content-type\": \"application/json\", ...headers },\n body\n });\n return [response.body, response] as const;\n };\n\n /**\n * Build the request once and return the fully-initialized context (after auth, tenant, CMS and\n * all consumer features): the request container, captured when the handler creates it, after\n * one request has run through it. For tests that resolve services directly from the container\n * rather than issuing GraphQL queries.\n */\n const getContext = async <\n C extends Record<string, any> = Record<string, any>\n >(): Promise<C> => {\n let requestContainer: Container | undefined;\n\n const ctxHandler = createTestHttpHandler({\n root: setupRoot,\n child: async container => {\n await setupRequest(container);\n GraphQLEngineFeature.register(container);\n requestContainer = container;\n }\n });\n\n await ctxHandler({\n method: \"POST\",\n path: \"/graphql\",\n headers: { \"x-tenant\": \"root\", \"content-type\": \"application/json\" },\n body: { query: \"{ __typename }\" }\n });\n\n return { container: requestContainer } as unknown as C;\n };\n\n // Convenience wrappers mirroring the retired `useGraphQLHandler` β thin builders over `invoke`\n // (the main `/graphql` endpoint) so consumers can issue typed queries/mutations directly.\n const createQuery = <T extends Record<string, any> = Record<string, any>>(query: string) => {\n return (variables?: Record<string, any>, headers: Record<string, string> = {}) =>\n invoke({ body: { query, variables: variables || undefined }, headers }) as Promise<\n readonly [T, any]\n >;\n };\n\n const createMutation = <T extends Record<string, any> = Record<string, any>>(\n mutation: string\n ) => {\n return (variables?: Record<string, any>, headers: Record<string, string> = {}) =>\n invoke({\n body: { query: mutation, variables: variables || undefined },\n headers\n }) as Promise<readonly [T, any]>;\n };\n\n const introspect = () => invoke({ body: { query: getIntrospectionQuery() } });\n\n return {\n handler,\n invoke,\n invokeCms,\n getContext,\n createQuery,\n createMutation,\n introspect,\n identity,\n tenant: { id: \"root\", name: \"Root\", parent: null }\n };\n};\n"],"names":["DEFAULT_IDENTITY","createCmsTestHandler","params","apiCoreStorage","getStorageOps","cmsStorage","identity","undefined","permissions","cmsType","setupRoot","container","TestIdentity","TestPermissions","TestAuthenticator","TestAuthorizer","AuthTriggerHandler","RootTenantInitializer","GraphQLSchemaCacheFeature","GraphQLSchemaKeyVerificationFeature","EncryptionKeyCacheFeature","JwksStoreFeature","setupRequest","wcpLicense","WcpLicenseLoader","createTestWcpLicense","registerApiCoreStorageOperations","logger","createLogger","Logger","ApiCoreFeature","processLegacyPlugins","legacyItems","Infinity","Boolean","isFn","p","legacyObjects","staticPlugins","HeadlessCmsFeature","plugin","handler","createTestHttpHandler","GraphQLEngineFeature","invoke","httpMethod","body","headers","response","invokeCms","type","getContext","requestContainer","ctxHandler","createQuery","query","variables","createMutation","mutation","introspect","getIntrospectionQuery"],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createCmsTestHandler } from "./createCmsTestHandler.js";
|
|
2
|
+
export type { CmsTestHandlerParams, InvokeParams } from "./createCmsTestHandler.js";
|
|
3
|
+
export { processLegacyPlugins } from "./processLegacyPlugins.js";
|
|
4
|
+
export { TestIdentity, TestAuthenticator } from "@webiny/api-core-testing";
|
|
5
|
+
export { TestPermissions, TestAuthorizer } from "@webiny/api-core-testing";
|
|
6
|
+
export { AuthTriggerHandler } from "@webiny/api-core-testing";
|
|
7
|
+
export { RootTenantInitializer } from "@webiny/api-core-testing";
|
package/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createCmsTestHandler } from "./createCmsTestHandler.js";
|
|
2
|
+
export { processLegacyPlugins } from "./processLegacyPlugins.js";
|
|
3
|
+
export { AuthTriggerHandler, RootTenantInitializer, TestAuthenticator, TestAuthorizer, TestIdentity, TestPermissions } from "@webiny/api-core-testing";
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webiny/api-headless-cms-testing",
|
|
3
|
+
"version": "0.0.0-unstable.0156a4c312",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./index.js",
|
|
7
|
+
"./*": "./*"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/webiny/webiny-js.git",
|
|
12
|
+
"directory": "packages/api-headless-cms-testing"
|
|
13
|
+
},
|
|
14
|
+
"description": "Shared Headless CMS integration test handler (createCmsTestHandler) for Webiny API packages.",
|
|
15
|
+
"author": "Webiny Ltd.",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@webiny/api-core": "0.0.0-unstable.0156a4c312",
|
|
19
|
+
"@webiny/api-core-testing": "0.0.0-unstable.0156a4c312",
|
|
20
|
+
"@webiny/api-graphql": "0.0.0-unstable.0156a4c312",
|
|
21
|
+
"@webiny/api-headless-cms": "0.0.0-unstable.0156a4c312",
|
|
22
|
+
"@webiny/di": "2.0.0",
|
|
23
|
+
"@webiny/event-handler-core": "0.0.0-unstable.0156a4c312",
|
|
24
|
+
"@webiny/logger": "0.0.0-unstable.0156a4c312",
|
|
25
|
+
"@webiny/wcp": "0.0.0-unstable.0156a4c312",
|
|
26
|
+
"graphql": "16.14.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@webiny/build-tools": "0.0.0-unstable.0156a4c312",
|
|
30
|
+
"rimraf": "6.1.3",
|
|
31
|
+
"typescript": "7.0.2"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"webiny": {
|
|
37
|
+
"publishFrom": "dist"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Container } from "@webiny/di";
|
|
2
|
+
/**
|
|
3
|
+
* Apply legacy RegisterExtensionPlugins (e.g. the storage-operations presets returned by
|
|
4
|
+
* getStorageOps(...).plugins) against the request container β the test-time equivalent of how the
|
|
5
|
+
* app registers them.
|
|
6
|
+
*
|
|
7
|
+
* Detection is by `plugin.type` rather than `instanceof RegisterExtensionPlugin`: a consumer's
|
|
8
|
+
* dependency tree can resolve a second copy of `@webiny/handler`, giving the storage-preset plugins
|
|
9
|
+
* a different class identity than the one this (shipped) module imports, which would make an
|
|
10
|
+
* `instanceof` check silently miss them.
|
|
11
|
+
*/
|
|
12
|
+
export declare function processLegacyPlugins(container: Container, plugins: any[]): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const REGISTER_EXTENSION_TYPE = "handler.register.extension";
|
|
2
|
+
function processLegacyPlugins(container, plugins) {
|
|
3
|
+
const flat = [
|
|
4
|
+
plugins
|
|
5
|
+
].flat(1 / 0);
|
|
6
|
+
for (const plugin of flat)if (plugin && plugin.type === REGISTER_EXTENSION_TYPE && "function" == typeof plugin.apply) plugin.apply({
|
|
7
|
+
container
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export { processLegacyPlugins };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=processLegacyPlugins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processLegacyPlugins.js","sources":["../src/processLegacyPlugins.ts"],"sourcesContent":["import type { Container } from \"@webiny/di\";\n\nconst REGISTER_EXTENSION_TYPE = \"handler.register.extension\";\n\n/**\n * Apply legacy RegisterExtensionPlugins (e.g. the storage-operations presets returned by\n * getStorageOps(...).plugins) against the request container β the test-time equivalent of how the\n * app registers them.\n *\n * Detection is by `plugin.type` rather than `instanceof RegisterExtensionPlugin`: a consumer's\n * dependency tree can resolve a second copy of `@webiny/handler`, giving the storage-preset plugins\n * a different class identity than the one this (shipped) module imports, which would make an\n * `instanceof` check silently miss them.\n */\nexport function processLegacyPlugins(container: Container, plugins: any[]): void {\n const flat = [plugins].flat(Infinity as 1);\n for (const plugin of flat) {\n if (\n plugin &&\n plugin.type === REGISTER_EXTENSION_TYPE &&\n typeof plugin.apply === \"function\"\n ) {\n plugin.apply({ container } as any);\n }\n }\n}\n"],"names":["REGISTER_EXTENSION_TYPE","processLegacyPlugins","container","plugins","flat","Infinity","plugin"],"mappings":"AAEA,MAAMA,0BAA0B;AAYzB,SAASC,qBAAqBC,SAAoB,EAAEC,OAAc;IACrE,MAAMC,OAAO;QAACD;KAAQ,CAAC,IAAI,CAACE,KAAQ;IACpC,KAAK,MAAMC,UAAUF,KACjB,IACIE,UACAA,OAAO,IAAI,KAAKN,2BAChB,AAAwB,cAAxB,OAAOM,OAAO,KAAK,EAEnBA,OAAO,KAAK,CAAC;QAAEJ;IAAU;AAGrC"}
|