From a11bb5b45235a2a549067a0e196cc7089c7cfc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Zori=C4=87?= Date: Tue, 15 Oct 2024 12:00:16 +0200 Subject: [PATCH] feat(api-serverless-cms): base tests (#4332) --- .github/workflows/pullRequests.yml | 9 +- .github/workflows/pushDev.yml | 9 +- .github/workflows/pushNext.yml | 9 +- .../wac/utils/listPackagesWithJestTests.ts | 7 + .../__tests__/testHelpers/plugins.ts | 2 +- .../src/graphql/graphQLHandlerFactory.ts | 4 +- .../api-headless-cms/src/graphql/index.ts | 5 +- packages/api-headless-cms/src/index.ts | 5 +- .../__tests__/handlers/graphQlHandler.ts | 63 ++++ .../__tests__/handlers/graphql/index.ts | 13 + .../__tests__/handlers/graphql/install.ts | 270 +++++++++++++++++ .../__tests__/handlers/helpers/core.ts | 203 +++++++++++++ .../enableBenchmarkOnEnvironmentVariable.ts | 9 + .../handlers/helpers/factory/index.ts | 3 + .../handlers/helpers/factory/mutation.ts | 16 + .../handlers/helpers/factory/query.ts | 16 + .../handlers/helpers/factory/types.ts | 59 ++++ .../__tests__/handlers/helpers/invoke.ts | 38 +++ .../handlers/helpers/lambdaContext.ts | 27 ++ .../__tests__/handlers/helpers/permissions.ts | 41 +++ .../handlers/helpers/tenancySecurity.ts | 102 +++++++ .../__tests__/handlers/types.ts | 22 ++ .../__tests__/install.test.ts | 278 ++++++++++++++++++ .../__tests__/introspect.test.ts | 52 ++++ packages/api-serverless-cms/jest.setup.js | 16 + packages/api-serverless-cms/package.json | 15 + .../api-serverless-cms/tsconfig.build.json | 16 +- packages/api-serverless-cms/tsconfig.json | 50 +++- .../src/createGraphQLHandler.ts | 7 +- packages/handler-graphql/src/index.ts | 5 +- yarn.lock | 15 + 31 files changed, 1362 insertions(+), 24 deletions(-) create mode 100644 packages/api-serverless-cms/__tests__/handlers/graphQlHandler.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/graphql/index.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/graphql/install.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/core.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/enableBenchmarkOnEnvironmentVariable.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/factory/index.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/factory/mutation.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/factory/query.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/factory/types.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/invoke.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/lambdaContext.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/permissions.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/helpers/tenancySecurity.ts create mode 100644 packages/api-serverless-cms/__tests__/handlers/types.ts create mode 100644 packages/api-serverless-cms/__tests__/install.test.ts create mode 100644 packages/api-serverless-cms/__tests__/introspect.test.ts create mode 100644 packages/api-serverless-cms/jest.setup.js diff --git a/.github/workflows/pullRequests.yml b/.github/workflows/pullRequests.yml index d9e3f796256..3882afd08db 100644 --- a/.github/workflows/pullRequests.yml +++ b/.github/workflows/pullRequests.yml @@ -238,7 +238,8 @@ jobs: --storage=ddb","storage":"ddb","id":"api-page-builder-import-export_ddb"},{"cmd":"packages/api-prerendering-service --storage=ddb","storage":"ddb","id":"api-prerendering-service_ddb"},{"cmd":"packages/api-security --storage=ddb","storage":"ddb","id":"api-security_ddb"},{"cmd":"packages/api-security-cognito - --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-tenancy + --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb","storage":"ddb","id":"api-serverless-cms_ddb"},{"cmd":"packages/api-tenancy --storage=ddb","storage":"ddb","id":"api-tenancy_ddb"},{"cmd":"packages/api-tenant-manager --storage=ddb","storage":"ddb","id":"api-tenant-manager_ddb"},{"cmd":"packages/tasks --storage=ddb","storage":"ddb","id":"tasks_ddb"}]') }} @@ -301,7 +302,8 @@ jobs: --storage=ddb-es,ddb","storage":"ddb-es","id":"api-mailer_ddb-es_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder_ddb-es_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-aco_ddb-es_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-serverless-cms_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-es,ddb","storage":"ddb-es","id":"tasks_ddb-es_ddb"}]') }} runs-on: ${{ matrix.os }} @@ -374,7 +376,8 @@ jobs: --storage=ddb-os,ddb","storage":"ddb-os","id":"api-mailer_ddb-os_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder_ddb-os_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-aco_ddb-os_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-serverless-cms_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-os,ddb","storage":"ddb-os","id":"tasks_ddb-os_ddb"}]') }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/pushDev.yml b/.github/workflows/pushDev.yml index baecf806069..862e06cf516 100644 --- a/.github/workflows/pushDev.yml +++ b/.github/workflows/pushDev.yml @@ -204,7 +204,8 @@ jobs: --storage=ddb","storage":"ddb","id":"api-page-builder-import-export_ddb"},{"cmd":"packages/api-prerendering-service --storage=ddb","storage":"ddb","id":"api-prerendering-service_ddb"},{"cmd":"packages/api-security --storage=ddb","storage":"ddb","id":"api-security_ddb"},{"cmd":"packages/api-security-cognito - --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-tenancy + --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb","storage":"ddb","id":"api-serverless-cms_ddb"},{"cmd":"packages/api-tenancy --storage=ddb","storage":"ddb","id":"api-tenancy_ddb"},{"cmd":"packages/api-tenant-manager --storage=ddb","storage":"ddb","id":"api-tenant-manager_ddb"},{"cmd":"packages/tasks --storage=ddb","storage":"ddb","id":"tasks_ddb"}]') }} @@ -267,7 +268,8 @@ jobs: --storage=ddb-es,ddb","storage":"ddb-es","id":"api-mailer_ddb-es_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder_ddb-es_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-aco_ddb-es_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-serverless-cms_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-es,ddb","storage":"ddb-es","id":"tasks_ddb-es_ddb"}]') }} runs-on: ${{ matrix.os }} @@ -339,7 +341,8 @@ jobs: --storage=ddb-os,ddb","storage":"ddb-os","id":"api-mailer_ddb-os_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder_ddb-os_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-aco_ddb-os_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-serverless-cms_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-os,ddb","storage":"ddb-os","id":"tasks_ddb-os_ddb"}]') }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/pushNext.yml b/.github/workflows/pushNext.yml index 4faab0d7c08..4ba8829dedc 100644 --- a/.github/workflows/pushNext.yml +++ b/.github/workflows/pushNext.yml @@ -204,7 +204,8 @@ jobs: --storage=ddb","storage":"ddb","id":"api-page-builder-import-export_ddb"},{"cmd":"packages/api-prerendering-service --storage=ddb","storage":"ddb","id":"api-prerendering-service_ddb"},{"cmd":"packages/api-security --storage=ddb","storage":"ddb","id":"api-security_ddb"},{"cmd":"packages/api-security-cognito - --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-tenancy + --storage=ddb","storage":"ddb","id":"api-security-cognito_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb","storage":"ddb","id":"api-serverless-cms_ddb"},{"cmd":"packages/api-tenancy --storage=ddb","storage":"ddb","id":"api-tenancy_ddb"},{"cmd":"packages/api-tenant-manager --storage=ddb","storage":"ddb","id":"api-tenant-manager_ddb"},{"cmd":"packages/tasks --storage=ddb","storage":"ddb","id":"tasks_ddb"}]') }} @@ -267,7 +268,8 @@ jobs: --storage=ddb-es,ddb","storage":"ddb-es","id":"api-mailer_ddb-es_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder_ddb-es_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-aco_ddb-es_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-page-builder-so-ddb-es_ddb-es_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-es,ddb","storage":"ddb-es","id":"api-serverless-cms_ddb-es_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-es,ddb","storage":"ddb-es","id":"tasks_ddb-es_ddb"}]') }} runs-on: ${{ matrix.os }} @@ -339,7 +341,8 @@ jobs: --storage=ddb-os,ddb","storage":"ddb-os","id":"api-mailer_ddb-os_ddb"},{"cmd":"packages/api-page-builder --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder_ddb-os_ddb"},{"cmd":"packages/api-page-builder-aco --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-aco_ddb-os_ddb"},{"cmd":"packages/api-page-builder-so-ddb-es - --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-page-builder-so-ddb-es_ddb-os_ddb"},{"cmd":"packages/api-serverless-cms + --storage=ddb-os,ddb","storage":"ddb-os","id":"api-serverless-cms_ddb-os_ddb"},{"cmd":"packages/migrations","storage":["ddb-es","ddb-os"],"id":"migrations"},{"cmd":"packages/tasks --storage=ddb-os,ddb","storage":"ddb-os","id":"tasks_ddb-os_ddb"}]') }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/wac/utils/listPackagesWithJestTests.ts b/.github/workflows/wac/utils/listPackagesWithJestTests.ts index 82d69873373..8603a34adb0 100644 --- a/.github/workflows/wac/utils/listPackagesWithJestTests.ts +++ b/.github/workflows/wac/utils/listPackagesWithJestTests.ts @@ -283,6 +283,13 @@ const CUSTOM_HANDLERS: Record Array> = { storage: "ddb-os" } ]; + }, + "api-serverless-cms": () => { + return [ + { cmd: "packages/api-serverless-cms --storage=ddb-es,ddb", storage: "ddb-es" }, + { cmd: "packages/api-serverless-cms --storage=ddb-os,ddb", storage: "ddb-os" }, + { cmd: "packages/api-serverless-cms --storage=ddb", storage: "ddb" } + ]; } }; diff --git a/packages/api-headless-cms/__tests__/testHelpers/plugins.ts b/packages/api-headless-cms/__tests__/testHelpers/plugins.ts index f2c2b8ae69b..b9c6dd32ca0 100644 --- a/packages/api-headless-cms/__tests__/testHelpers/plugins.ts +++ b/packages/api-headless-cms/__tests__/testHelpers/plugins.ts @@ -22,7 +22,7 @@ export interface CreateHandlerCoreParams { topPlugins?: Plugin | Plugin[] | Plugin[][] | PluginCollection; plugins?: Plugin | Plugin[] | Plugin[][] | PluginCollection; bottomPlugins?: Plugin | Plugin[] | Plugin[][] | PluginCollection; - path?: `manage/${string}-${string}}` | `read/${string}-${string}}` | string; + path?: `manage/${string}-${string}` | `read/${string}-${string}` | string; } export const createHandlerCore = (params: CreateHandlerCoreParams) => { const tenant = { diff --git a/packages/api-headless-cms/src/graphql/graphQLHandlerFactory.ts b/packages/api-headless-cms/src/graphql/graphQLHandlerFactory.ts index d5dafac9375..e8b8cf3098d 100644 --- a/packages/api-headless-cms/src/graphql/graphQLHandlerFactory.ts +++ b/packages/api-headless-cms/src/graphql/graphQLHandlerFactory.ts @@ -1,6 +1,6 @@ import debugPlugins from "@webiny/handler-graphql/debugPlugins"; import { CmsContext } from "~/types"; -import { PluginCollection } from "@webiny/plugins/types"; +import { Plugin } from "@webiny/plugins/types"; import { RoutePlugin } from "@webiny/handler"; import { handleRequest } from "./handleRequest"; @@ -20,7 +20,7 @@ const cmsRoutes = new RoutePlugin(({ onPost, onOptions, context }) = cmsRoutes.name = "headless-cms.graphql.route.default"; -export const graphQLHandlerFactory = ({ debug }: GraphQLHandlerFactoryParams): PluginCollection => { +export const graphQLHandlerFactory = ({ debug }: GraphQLHandlerFactoryParams): Plugin[] => { return [ ...(debug ? debugPlugins() : []), cmsRoutes, diff --git a/packages/api-headless-cms/src/graphql/index.ts b/packages/api-headless-cms/src/graphql/index.ts index c33b847df62..6586cfaf4b3 100644 --- a/packages/api-headless-cms/src/graphql/index.ts +++ b/packages/api-headless-cms/src/graphql/index.ts @@ -1,8 +1,9 @@ +import { Plugin } from "@webiny/plugins/types"; import { createSystemSchemaPlugin } from "./system"; import { graphQLHandlerFactory, GraphQLHandlerFactoryParams } from "./graphQLHandlerFactory"; import { createBaseSchema } from "~/graphql/schema/baseSchema"; export type CreateGraphQLParams = GraphQLHandlerFactoryParams; -export const createGraphQL = (params: CreateGraphQLParams) => { - return [createBaseSchema(), createSystemSchemaPlugin(), graphQLHandlerFactory(params)]; +export const createGraphQL = (params: CreateGraphQLParams): Plugin[] => { + return [createBaseSchema(), createSystemSchemaPlugin(), ...graphQLHandlerFactory(params)]; }; diff --git a/packages/api-headless-cms/src/index.ts b/packages/api-headless-cms/src/index.ts index c21c4d40901..0ecc13bad72 100644 --- a/packages/api-headless-cms/src/index.ts +++ b/packages/api-headless-cms/src/index.ts @@ -18,15 +18,16 @@ import { createExportGraphQL } from "~/export"; import { createStorageTransform } from "~/storage"; import { createLexicalHTMLRenderer } from "./htmlRenderer/createLexicalHTMLRenderer"; import { createRevisionIdScalarPlugin } from "~/graphql/scalars/RevisionIdScalarPlugin"; +import { Plugin } from "@webiny/plugins/types"; export * from "./utils/isHeadlessCmsReady"; export * from "./utils/createModelField"; export * from "./graphql/schema/resolvers/manage/normalizeGraphQlInput"; export type CreateHeadlessCmsGraphQLParams = CreateGraphQLParams; -export const createHeadlessCmsGraphQL = (params: CreateHeadlessCmsGraphQLParams = {}) => { +export const createHeadlessCmsGraphQL = (params: CreateHeadlessCmsGraphQLParams = {}): Plugin[] => { return [ - createRevisionIdScalarPlugin(), + ...createRevisionIdScalarPlugin(), /** * PathParameter plugins are used to determine the type of the cms endpoint */ diff --git a/packages/api-serverless-cms/__tests__/handlers/graphQlHandler.ts b/packages/api-serverless-cms/__tests__/handlers/graphQlHandler.ts new file mode 100644 index 00000000000..3c1f7354ad8 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/graphQlHandler.ts @@ -0,0 +1,63 @@ +import { createHandler } from "@webiny/handler-aws"; +import { createInvoke } from "./helpers/invoke"; +import { createLambdaContext } from "./helpers/lambdaContext"; +import { Plugin } from "@webiny/plugins/types"; +import { createCore } from "./helpers/core"; +import { PathType } from "./types"; +import { getIntrospectionQuery } from "graphql"; +import { createGraphQl } from "./graphql"; +import { createQueryFactory } from "~tests/handlers/helpers/factory"; +import { createMutationFactory } from "~tests/handlers/helpers/factory/mutation"; + +export interface IGraphQlHandlerParams { + path: PathType; + plugins?: Plugin[]; + features?: boolean | string[]; +} + +export const useGraphQlHandler = (params: IGraphQlHandlerParams) => { + const core = createCore({ + ...params + }); + + const handler = createHandler({ + plugins: core.plugins, + debug: process.env.DEBUG === "true" + }); + + const invoke = createInvoke({ + handler, + path: params.path, + lambdaContext: createLambdaContext() + }); + + const createQuery = createQueryFactory({ + invoke + }); + const createMutation = createMutationFactory({ + invoke + }); + + return { + invoke, + async introspect() { + return invoke({ + body: { + query: getIntrospectionQuery() + } + }); + }, + handler, + cmsStorage: core.cmsStorage, + i18nStorage: core.i18nStorage, + pageBuilderStorage: core.pageBuilderStorage, + fileManagerStorage: core.fileManagerStorage, + securityStorage: core.securityStorage, + tenancyStorage: core.tenancyStorage, + login: core.login, + ...createGraphQl({ + createQuery, + createMutation + }) + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/graphql/index.ts b/packages/api-serverless-cms/__tests__/handlers/graphql/index.ts new file mode 100644 index 00000000000..c4a7c3dd941 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/graphql/index.ts @@ -0,0 +1,13 @@ +import { createInstallGraphQL } from "./install"; +import { ICreateMutationCb, ICreateQueryCb } from "~tests/handlers/helpers/factory/types"; + +export interface ICreateGraphQlParams { + createQuery: ICreateQueryCb; + createMutation: ICreateMutationCb; +} + +export const createGraphQl = (params: ICreateGraphQlParams) => { + return { + ...createInstallGraphQL(params) + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/graphql/install.ts b/packages/api-serverless-cms/__tests__/handlers/graphql/install.ts new file mode 100644 index 00000000000..3cf353686ed --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/graphql/install.ts @@ -0,0 +1,270 @@ +import { ICreateMutationCb, ICreateQueryCb, MutationBody } from "../helpers/factory/types"; + +/** + * Queries + */ +const createIsSecurityInstalled = () => { + return ` + query IsSecurityInstalled { + security { + version + } + } + `; +}; +const createIsTenancyInstalled = () => { + return ` + query IsTenancyInstalled { + tenancy { + version + } + } + `; +}; + +const createIsLocaleInstalled = () => { + return ` + query IsLocaleInstalled { + i18n { + version + } + } + `; +}; + +const createIsAdminUsersInstalled = () => { + return ` + query IsAdminUsersInstalled { + adminUsers { + version + } + } + `; +}; + +const createIsHeadlessCmsInstalled = () => { + return ` + query IsHeadlessCmsInstalled { + cms { + version + } + } + `; +}; + +const createIsPageBuilderInstalled = () => { + return ` + query IsPageBuilderInstalled { + pageBuilder { + version + } + } + `; +}; + +const createIsFormBuilderInstalled = () => { + return ` + query IsFormBuilderInstalled { + formBuilder { + version + } + } + `; +}; + +/** + * Mutations + */ + +const createInstallSecurityMutation = (): MutationBody => { + return `mutation InstallSecurity { + security { + install { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +const createInstallTenancyMutation = (): MutationBody => { + return `mutation InstallTenancy { + tenancy { + install { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +const createInstallAdminUsersMutation = (): MutationBody => { + return `mutation InstallAdminUsers { + adminUsers { + install { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +export interface IInstallI18NVariables { + data: { + code: string; + }; +} + +const createInstallI18NMutation = (): MutationBody => { + return `mutation InstallLocale($data: I18NInstallInput!) { + i18n { + install(data: $data) { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +const createInstallHeadlessCmsMutation = (): MutationBody => { + return `mutation InstallHeadlessCms { + cms { + install { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +export interface IInstallPageBuilderVariables { + data: { + insertDemoData: boolean; + websiteUrl: string; + name: string; + }; +} + +const createInstallPageBuilderMutation = (): MutationBody => { + return `mutation InstallPageBuilder($data: PbInstallInput!) { + pageBuilder { + install(data: $data) { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +const createInstallFormBuilderMutation = (): MutationBody => { + return `mutation InstallFormBuilder { + formBuilder { + install { + data + error { + message + code + data + stack + } + } + } + } + `; +}; + +export interface ICreateInstallGraphQlParams { + createQuery: ICreateQueryCb; + createMutation: ICreateMutationCb; +} + +export const createInstallGraphQL = (params: ICreateInstallGraphQlParams) => { + const { createQuery, createMutation } = params; + + return { + /** + * Is Installed Queries + */ + isSecurityInstalled: createQuery({ + query: createIsSecurityInstalled() + }), + isTenancyInstalled: createQuery({ + query: createIsTenancyInstalled() + }), + isAdminUsersInstalled: createQuery({ + query: createIsAdminUsersInstalled() + }), + isLocaleInstalled: createQuery({ + query: createIsLocaleInstalled() + }), + isHeadlessCmsInstalled: createQuery({ + query: createIsHeadlessCmsInstalled() + }), + isPageBuilderInstalled: createQuery({ + query: createIsPageBuilderInstalled() + }), + isFormBuilderInstalled: createQuery({ + query: createIsFormBuilderInstalled() + }), + /** + * Install Mutations + */ + installSecurity: createMutation({ + mutation: createInstallSecurityMutation() + }), + installTenancy: createMutation({ + mutation: createInstallTenancyMutation() + }), + installAdminUsers: createMutation({ + mutation: createInstallAdminUsersMutation() + }), + installI18N: createMutation({ + mutation: createInstallI18NMutation() + }), + installHeadlessCms: createMutation({ + mutation: createInstallHeadlessCmsMutation() + }), + installPageBuilder: createMutation({ + mutation: createInstallPageBuilderMutation() + }), + installFormBuilder: createMutation({ + mutation: createInstallFormBuilderMutation() + }) + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/core.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/core.ts new file mode 100644 index 00000000000..46d81960218 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/core.ts @@ -0,0 +1,203 @@ +import { Plugin } from "@webiny/plugins/types"; +import { getStorageOps, PluginCollection } from "@webiny/project-utils/testing/environment"; +import { HeadlessCmsStorageOperations } from "@webiny/api-headless-cms/types"; +import { SecurityIdentity, SecurityStorageOperations } from "@webiny/api-security/types"; +import apiKeyAuthentication from "@webiny/api-security/plugins/apiKeyAuthentication"; +import apiKeyAuthorization from "@webiny/api-security/plugins/apiKeyAuthorization"; +import i18nContext from "@webiny/api-i18n/graphql/context"; +import { mockLocalesPlugins } from "@webiny/api-i18n/graphql/testing"; +import { createHeadlessCmsContext, createHeadlessCmsGraphQL } from "@webiny/api-headless-cms"; +import graphQLHandlerPlugins from "@webiny/handler-graphql"; +import { enableBenchmarkOnEnvironmentVariable } from "./enableBenchmarkOnEnvironmentVariable"; +import { createWcpContext, createWcpGraphQL } from "@webiny/api-wcp"; +import { createTenancyAndSecurity } from "./tenancySecurity"; +import { createPermissions, Permission } from "./permissions"; +import { PathType } from "../types"; +import { TenancyStorageOperations, Tenant } from "@webiny/api-tenancy/types"; +import { I18NLocalesStorageOperations } from "@webiny/api-i18n/types"; +import { PageBuilderStorageOperations } from "@webiny/api-page-builder/types"; +import { FileManagerStorageOperations } from "@webiny/api-file-manager/types"; +import { AdminUsersStorageOperations } from "@webiny/api-admin-users/types"; +import createAdminUsersApp from "@webiny/api-admin-users"; +import i18nPlugins from "@webiny/api-i18n/graphql"; +import { + createPageBuilderContext, + createPageBuilderGraphQL +} from "@webiny/api-page-builder/graphql"; +import { createWebsockets } from "@webiny/api-websockets"; +import { createRecordLocking } from "@webiny/api-record-locking"; + +import { createFormBuilder } from "@webiny/api-form-builder"; +import { FormBuilderStorageOperations } from "@webiny/api-form-builder/types"; +import { createFileManagerContext, createFileManagerGraphQL } from "@webiny/api-file-manager"; +import { createAco } from "@webiny/api-aco"; +import { createAcoPageBuilderContext } from "@webiny/api-page-builder-aco"; +import { createAuditLogs } from "@webiny/api-audit-logs"; +import { createAcoHcmsContext } from "@webiny/api-headless-cms-aco"; +import { createHcmsTasks } from "@webiny/api-headless-cms-tasks"; +import { createApwGraphQL, createApwPageBuilderContext } from "@webiny/api-apw"; +import { ApwScheduleActionStorageOperations } from "@webiny/api-apw/scheduler/types"; +import { createBackgroundTaskContext, createBackgroundTaskGraphQL } from "@webiny/tasks"; +import { ContextPlugin } from "@webiny/api"; +import pageBuilderImportExportPlugins from "@webiny/api-page-builder-import-export/graphql"; +import { createStorageOperations as createPageBuilderImportExportStorageOperations } from "@webiny/api-page-builder-import-export-so-ddb"; +import { Context } from "~/index"; +import { getDocumentClient } from "@webiny/project-utils/testing/dynamodb"; + +export interface ICreateCoreParams { + plugins?: Plugin[]; + path: PathType; + permissions?: Permission[]; + tenant?: Pick; + features?: boolean | string[]; +} + +export interface ICreateCoreResult { + plugins: PluginCollection; + cmsStorage: HeadlessCmsStorageOperations; + i18nStorage: I18NLocalesStorageOperations; + pageBuilderStorage: PageBuilderStorageOperations; + formBuilderStorage: FormBuilderStorageOperations; + fileManagerStorage: FileManagerStorageOperations; + securityStorage: SecurityStorageOperations; + tenancyStorage: TenancyStorageOperations; + adminUsersStorage: AdminUsersStorageOperations; + tenant: Pick; + login: (identity?: SecurityIdentity | null) => void; +} + +export const createCore = (params: ICreateCoreParams): ICreateCoreResult => { + const { permissions, tenant, plugins = [], features } = params; + + const documentClient = getDocumentClient(); + + const cmsStorage = getStorageOps("cms"); + const i18nStorage = getStorageOps("i18n"); + const pageBuilderStorage = getStorageOps("pageBuilder"); + const formBuilderStorage = getStorageOps("formBuilder"); + const fileManagerStorage = getStorageOps("fileManager"); + const securityStorage = getStorageOps("security"); + const tenancyStorage = getStorageOps("tenancy"); + const adminUsersStorage = getStorageOps("adminUsers"); + const apwScheduleStorage = getStorageOps("apwSchedule"); + + const security = createTenancyAndSecurity({ + permissions: createPermissions(permissions), + tenant + }); + + return { + cmsStorage: cmsStorage.storageOperations, + i18nStorage: i18nStorage.storageOperations, + pageBuilderStorage: pageBuilderStorage.storageOperations, + formBuilderStorage: formBuilderStorage.storageOperations, + fileManagerStorage: fileManagerStorage.storageOperations, + securityStorage: securityStorage.storageOperations, + tenancyStorage: tenancyStorage.storageOperations, + adminUsersStorage: adminUsersStorage.storageOperations, + tenant: security.tenant, + login: security.login, + plugins: [ + enableBenchmarkOnEnvironmentVariable(), + createWcpContext(), + createWcpGraphQL(), + new ContextPlugin(async context => { + if (!features) { + return; + } + + const canUse = (name: string): boolean => { + if (features === true) { + return true; + } else if (!Array.isArray(features) || !features.includes(name)) { + return false; + } + return true; + }; + + context.wcp = { + ensureCanUseFeature: () => void 0, + canUseFolderLevelPermissions: () => { + if (!canUse("advancedAccessControlLayer")) { + return false; + } + // @ts-expect-error + return !!context.project?.package?.features?.advancedAccessControlLayer + ?.options?.folderLevelPermissions; + }, + canUseAacl: () => { + return canUse("advancedAccessControlLayer"); + }, + canUsePrivateFiles: () => true, + canUseTeams: () => true, + decrementSeats: async () => void 0, + incrementSeats: async () => void 0, + decrementTenants: async () => void 0, + incrementTenants: async () => void 0, + getProjectEnvironment: () => null, + getProjectLicense: () => null, + canUseFeature: canUse, + canUseRecordLocking: () => { + return canUse("recordLocking"); + } + }; + }), + ...cmsStorage.plugins, + ...pageBuilderStorage.plugins, + ...fileManagerStorage.plugins, + ...securityStorage.plugins, + ...tenancyStorage.plugins, + ...adminUsersStorage.plugins, + ...security.plugins, + createAdminUsersApp({ + storageOperations: adminUsersStorage.storageOperations + }), + apiKeyAuthentication({ identityType: "api-key" }), + apiKeyAuthorization({ identityType: "api-key" }), + i18nContext(), + i18nPlugins(), + /** + * We are 100% positive that storageOperations is a list of plugins, so we can safely spread it. + */ + // @ts-expect-error + ...i18nStorage.storageOperations, + ...i18nStorage.plugins, + mockLocalesPlugins(), + createHeadlessCmsContext({ + storageOperations: cmsStorage.storageOperations + }), + createHeadlessCmsGraphQL(), + createPageBuilderContext({ + storageOperations: pageBuilderStorage.storageOperations + }), + createPageBuilderGraphQL(), + createFileManagerContext({ + storageOperations: fileManagerStorage.storageOperations + }), + createFileManagerGraphQL(), + createFormBuilder({ + storageOperations: formBuilderStorage.storageOperations + }), + pageBuilderImportExportPlugins({ + storageOperations: createPageBuilderImportExportStorageOperations({ + documentClient + }) + }), + createApwPageBuilderContext({ + storageOperations: apwScheduleStorage.storageOperations + }), + createAco(), + createAuditLogs(), + createRecordLocking(), + createWebsockets(), + ...createBackgroundTaskContext(), + ...createBackgroundTaskGraphQL(), + createAcoPageBuilderContext(), + createAcoHcmsContext(), + createHcmsTasks(), + createApwGraphQL(), + plugins, + graphQLHandlerPlugins() + ] + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/enableBenchmarkOnEnvironmentVariable.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/enableBenchmarkOnEnvironmentVariable.ts new file mode 100644 index 00000000000..3533f1ea6da --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/enableBenchmarkOnEnvironmentVariable.ts @@ -0,0 +1,9 @@ +import { ContextPlugin } from "@webiny/api"; + +export const enableBenchmarkOnEnvironmentVariable = () => { + return new ContextPlugin(async context => { + context.benchmark.enableOn(async () => { + return process.env.BENCHMARK_ENABLE === "true"; + }); + }); +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/factory/index.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/index.ts new file mode 100644 index 00000000000..f5ef1c1217c --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/index.ts @@ -0,0 +1,3 @@ +export * from "./mutation"; +export * from "./query"; +export * from "./types"; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/factory/mutation.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/mutation.ts new file mode 100644 index 00000000000..52221a26579 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/mutation.ts @@ -0,0 +1,16 @@ +import { ICreateMutationFactory } from "./types"; + +export const createMutationFactory: ICreateMutationFactory = ({ invoke }) => { + return ({ mutation }) => { + return params => { + return invoke({ + httpMethod: "POST", + headers: params?.headers, + body: { + query: mutation, + variables: params?.variables + } + }); + }; + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/factory/query.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/query.ts new file mode 100644 index 00000000000..208eee90879 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/query.ts @@ -0,0 +1,16 @@ +import { ICreateQueryFactory } from "./types"; + +export const createQueryFactory: ICreateQueryFactory = ({ invoke }) => { + return ({ query }) => { + return params => { + return invoke({ + httpMethod: "POST", + headers: params?.headers, + body: { + query, + variables: params?.variables + } + }); + }; + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/factory/types.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/types.ts new file mode 100644 index 00000000000..9b0ad27f579 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/factory/types.ts @@ -0,0 +1,59 @@ +import { GenericRecord } from "@webiny/api/types"; +import { IInvokeCb } from "~tests/handlers/types"; + +/** + * Query + */ +export interface IQueryParams { + variables?: T; + headers?: GenericRecord; +} + +export interface IQuery { + (params?: IQueryParams): Promise; +} + +export interface ICreateQueryCbParams { + query: string; +} + +export interface ICreateQueryCb { + (params: ICreateQueryCbParams): IQuery; +} + +export interface ICreateQueryFactoryParams { + invoke: IInvokeCb; +} + +export interface ICreateQueryFactory { + (params: ICreateQueryFactoryParams): ICreateQueryCb; +} + +/** + * Mutation + */ +export interface IMutationParams { + variables?: T; + headers?: GenericRecord; +} + +export interface IMutation { + (params?: IMutationParams): Promise; +} +export type MutationBody = `mutation ${string}`; + +export interface ICreateMutationCbParams { + mutation: MutationBody; +} + +export interface ICreateMutationCb { + (params: ICreateMutationCbParams): IMutation; +} + +export interface ICreateMutationFactoryParams { + invoke: IInvokeCb; +} + +export interface ICreateMutationFactory { + (params: ICreateMutationFactoryParams): ICreateMutationCb; +} diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/invoke.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/invoke.ts new file mode 100644 index 00000000000..1330a6b4d44 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/invoke.ts @@ -0,0 +1,38 @@ +import { createHandler } from "@webiny/handler-aws"; +import { APIGatewayEvent, LambdaContext } from "@webiny/handler-aws/types"; +import { IInvokeCb } from "~tests/handlers/types"; + +export interface ICreateInvokeParams { + handler: ReturnType; + path: string; + lambdaContext: LambdaContext; +} + +export const createInvoke = ({ + handler, + path, + lambdaContext +}: ICreateInvokeParams): IInvokeCb => { + return async params => { + const { httpMethod = "POST", headers, body, ...rest } = params; + const response = await handler( + { + /** + * If no path defined, use /graphql as we want to make request to main api + */ + path, + httpMethod, + headers: { + ["x-tenant"]: "root", + ["Content-Type"]: "application/json", + ...headers + }, + body: JSON.stringify(body), + ...rest + } as unknown as APIGatewayEvent, + lambdaContext + ); + // The first element is the response body, and the second is the raw response. + return [JSON.parse(response.body || "{}"), response]; + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/lambdaContext.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/lambdaContext.ts new file mode 100644 index 00000000000..4c154e1d478 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/lambdaContext.ts @@ -0,0 +1,27 @@ +import { LambdaContext } from "@webiny/handler-aws/types"; + +export const createLambdaContext = (input?: Partial): LambdaContext => { + return { + awsRequestId: "abc", + callbackWaitsForEmptyEventLoop: false, + functionName: "handler", + functionVersion: "1", + invokedFunctionArn: "xyz", + memoryLimitInMB: "512", + logGroupName: "custom", + logStreamName: "custom", + getRemainingTimeInMillis: () => { + return 15 * 60 * 60; + }, + done: () => { + return null; + }, + fail: () => { + return null; + }, + succeed: () => { + return null; + }, + ...input + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/permissions.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/permissions.ts new file mode 100644 index 00000000000..71971cd34fb --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/permissions.ts @@ -0,0 +1,41 @@ +import { SecurityIdentity } from "@webiny/api-security/types"; +import { GenericRecord } from "@webiny/api/types"; + +export interface Permission { + name: string; + locales?: string[]; + models?: GenericRecord; + groups?: GenericRecord; + rwd?: string; + pw?: string; + own?: boolean; + _src?: string; +} + +export const identity = { + id: "id-12345678", + displayName: "John Doe", + type: "admin" +}; + +const getSecurityIdentity = () => { + return identity; +}; + +export const createPermissions = (permissions?: Permission[]): Permission[] => { + if (permissions) { + return permissions; + } + return [ + { + name: "*" + } + ]; +}; + +export const createIdentity = (identity?: SecurityIdentity) => { + if (!identity) { + return getSecurityIdentity(); + } + return identity; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/helpers/tenancySecurity.ts b/packages/api-serverless-cms/__tests__/handlers/helpers/tenancySecurity.ts new file mode 100644 index 00000000000..0c810a7c967 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/helpers/tenancySecurity.ts @@ -0,0 +1,102 @@ +import { Context } from "~/index"; +import { Plugin } from "@webiny/plugins/Plugin"; +import { createTenancyContext, createTenancyGraphQL } from "@webiny/api-tenancy"; +import { createSecurityContext, createSecurityGraphQL } from "@webiny/api-security"; +import { + SecurityIdentity, + SecurityPermission, + SecurityStorageOperations +} from "@webiny/api-security/types"; +import { ContextPlugin } from "@webiny/api"; +import { getStorageOps } from "@webiny/project-utils/testing/environment"; +import { TenancyStorageOperations, Tenant } from "@webiny/api-tenancy/types"; + +interface IConfig { + permissions: SecurityPermission[]; + tenant?: Pick; +} + +export const getDefaultIdentity = (permissions: SecurityPermission[]): SecurityIdentity => { + return { + id: "id-12345678", + type: "admin", + displayName: "John Doe", + permissions + }; +}; + +export const getDefaultTenant = (tenant?: Partial): Tenant => { + return { + id: "root", + name: "Root", + parent: null, + createdOn: new Date().toISOString(), + savedOn: new Date().toISOString(), + description: "Root tenant", + status: "active", + tags: [], + settings: { + domains: [] + }, + ...tenant + }; +}; + +export interface ILogin { + identity: SecurityIdentity | null; + setIdentity: (identity: SecurityIdentity | null) => void; + getIdentity: () => SecurityIdentity | null; +} + +const login: ILogin = { + identity: null, + setIdentity: identity => { + login.identity = identity; + }, + getIdentity: () => { + return login.identity; + } +}; + +export const createTenancyAndSecurity = ({ permissions, tenant }: IConfig) => { + const tenancyStorage = getStorageOps("tenancy"); + const securityStorage = getStorageOps("security"); + + const plugins = [ + createTenancyContext({ storageOperations: tenancyStorage.storageOperations }), + createTenancyGraphQL(), + createSecurityContext({ storageOperations: securityStorage.storageOperations }), + createSecurityGraphQL(), + new ContextPlugin(async context => { + const identity = login.getIdentity(); + if (!identity) { + return; + } + context.tenancy.setCurrentTenant(getDefaultTenant(tenant)); + context.security.setIdentity(identity); + }), + new ContextPlugin(context => { + context.security.addAuthenticator(async () => { + return login.getIdentity(); + }); + + context.security.addAuthorizer(async () => { + const { headers = {} } = context.request || {}; + if (headers["authorization"]) { + return null; + } + + return login.getIdentity()?.permissions || null; + }); + }) + ].filter(Boolean) as Plugin[]; + return { + plugins, + tenant: getDefaultTenant(tenant), + login: (identity?: SecurityIdentity | null) => { + login.setIdentity( + identity === null ? null : identity || getDefaultIdentity(permissions) + ); + } + }; +}; diff --git a/packages/api-serverless-cms/__tests__/handlers/types.ts b/packages/api-serverless-cms/__tests__/handlers/types.ts new file mode 100644 index 00000000000..11207ceb55d --- /dev/null +++ b/packages/api-serverless-cms/__tests__/handlers/types.ts @@ -0,0 +1,22 @@ +import { GenericRecord } from "@webiny/api/types"; + +export type PathType = + | `/cms/manage/${string}` + | `/cms/read/${string}` + | `/cms/preview/${string}` + | "/graphql"; + +export interface IInvokeCbParams { + httpMethod?: "POST" | "GET" | "OPTIONS"; + body: { + query: string; + variables?: GenericRecord; + }; + headers?: GenericRecord; +} + +export type IInvokeCbResult = [T, string]; + +export interface IInvokeCb { + (params: IInvokeCbParams): Promise>; +} diff --git a/packages/api-serverless-cms/__tests__/install.test.ts b/packages/api-serverless-cms/__tests__/install.test.ts new file mode 100644 index 00000000000..19c42540d00 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/install.test.ts @@ -0,0 +1,278 @@ +import { useGraphQlHandler } from "./handlers/graphQlHandler"; + +jest.setTimeout(90000); + +describe("install", () => { + beforeEach(async () => { + process.env.S3_BUCKET = "a-mock-s3-bucket-which-does-not-exist"; + }); + + const wcpOptions = ["on", "off"]; + + it.each(wcpOptions)("should validate that no app is installed - wcp %s", async wcp => { + const { + isAdminUsersInstalled, + isTenancyInstalled, + isSecurityInstalled, + isHeadlessCmsInstalled, + isPageBuilderInstalled, + isFormBuilderInstalled, + isLocaleInstalled + } = useGraphQlHandler({ + path: "/graphql", + features: wcp === "on" ? true : false + }); + + const [isAdminUsersInstalledResult] = await isAdminUsersInstalled(); + + expect(isAdminUsersInstalledResult).toEqual({ + data: { + adminUsers: { + version: null + } + } + }); + + const [isTenancyInstalledResult] = await isTenancyInstalled(); + expect(isTenancyInstalledResult).toEqual({ + data: { + tenancy: { + version: null + } + } + }); + + const [isSecurityInstalledResult] = await isSecurityInstalled(); + expect(isSecurityInstalledResult).toEqual({ + data: { + security: { + version: null + } + } + }); + + const [isLocaleInstalledResult] = await isLocaleInstalled(); + expect(isLocaleInstalledResult).toEqual({ + data: { + i18n: { + version: null + } + } + }); + + const [isHeadlessCmsInstalledResult] = await isHeadlessCmsInstalled(); + + expect(isHeadlessCmsInstalledResult).toEqual({ + data: { + cms: { + version: null + } + } + }); + + const [isPageBuilderInstalledResult] = await isPageBuilderInstalled(); + expect(isPageBuilderInstalledResult).toEqual({ + data: { + pageBuilder: { + version: null + } + } + }); + + const [isFormBuilderInstalledResult] = await isFormBuilderInstalled(); + expect(isFormBuilderInstalledResult).toEqual({ + data: { + formBuilder: { + version: null + } + } + }); + }); + + it.each(wcpOptions)("should install system - wcp %s", async wcp => { + const { + installSecurity, + installTenancy, + installAdminUsers, + installFormBuilder, + installPageBuilder, + installHeadlessCms, + installI18N, + login, + isAdminUsersInstalled, + isTenancyInstalled, + isSecurityInstalled, + isHeadlessCmsInstalled, + isPageBuilderInstalled, + isFormBuilderInstalled, + isLocaleInstalled + } = useGraphQlHandler({ + path: "/graphql", + features: wcp === "on" ? true : false + }); + const [installTenancyResult] = await installTenancy(); + expect(installTenancyResult).toEqual({ + data: { + tenancy: { + install: { + data: true, + error: null + } + } + } + }); + + const [installSecurityResult] = await installSecurity(); + expect(installSecurityResult).toEqual({ + data: { + security: { + install: { + data: true, + error: null + } + } + } + }); + + login(); + + const [installAdminUsersResult] = await installAdminUsers(); + expect(installAdminUsersResult).toEqual({ + data: { + adminUsers: { + install: { + data: true, + error: null + } + } + } + }); + + const [installI18NResult] = await installI18N({ + variables: { + data: { + code: "en-US" + } + } + }); + expect(installI18NResult).toEqual({ + data: { + i18n: { + install: { + data: true, + error: null + } + } + } + }); + + const [installCmsResult] = await installHeadlessCms(); + expect(installCmsResult).toEqual({ + data: { + cms: { + install: { + data: true, + error: null + } + } + } + }); + + const [installPageBuilderResult] = await installPageBuilder({ + variables: { + data: { + insertDemoData: false, + name: "My Website", + websiteUrl: "https://www.webiny.com" + } + } + }); + expect(installPageBuilderResult).toEqual({ + data: { + pageBuilder: { + install: { + data: true, + error: null + } + } + } + }); + + const [installFormBuilderResult] = await installFormBuilder(); + expect(installFormBuilderResult).toEqual({ + data: { + formBuilder: { + install: { + data: true, + error: null + } + } + } + }); + + const [isAdminUsersInstalledResult] = await isAdminUsersInstalled(); + + expect(isAdminUsersInstalledResult).toEqual({ + data: { + adminUsers: { + version: "true" + } + } + }); + + const [isTenancyInstalledResult] = await isTenancyInstalled(); + expect(isTenancyInstalledResult).toEqual({ + data: { + tenancy: { + version: "true" + } + } + }); + + const [isSecurityInstalledResult] = await isSecurityInstalled(); + expect(isSecurityInstalledResult).toEqual({ + data: { + security: { + version: "true" + } + } + }); + + const [isLocaleInstalledResult] = await isLocaleInstalled(); + expect(isLocaleInstalledResult).toEqual({ + data: { + i18n: { + version: "true" + } + } + }); + + const [isHeadlessCmsInstalledResult] = await isHeadlessCmsInstalled(); + + expect(isHeadlessCmsInstalledResult).toEqual({ + data: { + cms: { + version: "true" + } + } + }); + + const [isPageBuilderInstalledResult] = await isPageBuilderInstalled(); + expect(isPageBuilderInstalledResult).toEqual({ + data: { + pageBuilder: { + version: "true" + } + } + }); + + const [isFormBuilderInstalledResult] = await isFormBuilderInstalled(); + expect(isFormBuilderInstalledResult).toEqual({ + data: { + formBuilder: { + version: "true" + } + } + }); + }); +}); diff --git a/packages/api-serverless-cms/__tests__/introspect.test.ts b/packages/api-serverless-cms/__tests__/introspect.test.ts new file mode 100644 index 00000000000..15d3cb48d27 --- /dev/null +++ b/packages/api-serverless-cms/__tests__/introspect.test.ts @@ -0,0 +1,52 @@ +import { useGraphQlHandler } from "./handlers/graphQlHandler"; +import { PathType } from "~tests/handlers/types"; + +jest.setTimeout(90000); + +type Option = ["on" | "off", PathType]; + +describe("introspect", () => { + const options: Option[] = [ + ["on", "/graphql"], + ["on", "/cms/manage/en-US"], + ["on", "/cms/preview/en-US"], + ["on", "/cms/read/en-US"], + ["off", "/graphql"], + ["off", "/cms/manage/en-US"], + ["off", "/cms/preview/en-US"], + ["off", "/cms/read/en-US"] + ]; + + beforeEach(async () => { + process.env.S3_BUCKET = "a-mock-s3-bucket-which-does-not-exist"; + }); + + it.each(options)( + "should properly introspect, with wcp %s, the %s endpoint", + async (wcp, path) => { + const { introspect, login } = useGraphQlHandler({ + path, + features: wcp === "on" ? true : false + }); + + login(); + + const [response] = await introspect(); + expect(response).toEqual({ + data: { + __schema: { + directives: expect.any(Array), + mutationType: { + name: "Mutation" + }, + queryType: { + name: "Query" + }, + subscriptionType: null, + types: expect.any(Array) + } + } + }); + } + ); +}); diff --git a/packages/api-serverless-cms/jest.setup.js b/packages/api-serverless-cms/jest.setup.js new file mode 100644 index 00000000000..da67dd38d79 --- /dev/null +++ b/packages/api-serverless-cms/jest.setup.js @@ -0,0 +1,16 @@ +const base = require("../../jest.config.base"); +const presets = require("@webiny/project-utils/testing/presets")( + ["@webiny/api-i18n", "storage-operations"], + ["@webiny/api-headless-cms", "storage-operations"], + ["@webiny/api-page-builder", "storage-operations"], + ["@webiny/api-form-builder", "storage-operations"], + ["@webiny/api-file-manager", "storage-operations"], + ["@webiny/api-security", "storage-operations"], + ["@webiny/api-tenancy", "storage-operations"], + ["@webiny/api-admin-users", "storage-operations"], + ["@webiny/api-apw", "storage-operations"] +); + +module.exports = { + ...base({ path: __dirname }, presets) +}; diff --git a/packages/api-serverless-cms/package.json b/packages/api-serverless-cms/package.json index 9c64c98fffe..de759af77b8 100644 --- a/packages/api-serverless-cms/package.json +++ b/packages/api-serverless-cms/package.json @@ -30,8 +30,23 @@ "@babel/core": "^7.24.0", "@babel/preset-env": "^7.24.0", "@babel/preset-typescript": "^7.23.3", + "@webiny/api-admin-users": "0.0.0", + "@webiny/api-apw": "0.0.0", + "@webiny/api-audit-logs": "0.0.0", + "@webiny/api-headless-cms-aco": "0.0.0", + "@webiny/api-headless-cms-tasks": "0.0.0", + "@webiny/api-page-builder-import-export": "0.0.0", + "@webiny/api-page-builder-import-export-so-ddb": "0.0.0", + "@webiny/api-record-locking": "0.0.0", + "@webiny/api-wcp": "0.0.0", + "@webiny/api-websockets": "0.0.0", "@webiny/cli": "0.0.0", + "@webiny/handler": "0.0.0", + "@webiny/handler-aws": "0.0.0", + "@webiny/plugins": "0.0.0", "@webiny/project-utils": "0.0.0", + "@webiny/tasks": "0.0.0", + "graphql": "^15.8.0", "rimraf": "^5.0.5", "ttypescript": "^1.5.13", "typescript": "4.9.5" diff --git a/packages/api-serverless-cms/tsconfig.build.json b/packages/api-serverless-cms/tsconfig.build.json index 7f615370901..710533d52f4 100644 --- a/packages/api-serverless-cms/tsconfig.build.json +++ b/packages/api-serverless-cms/tsconfig.build.json @@ -16,7 +16,21 @@ { "path": "../api-tenancy/tsconfig.build.json" }, { "path": "../error/tsconfig.build.json" }, { "path": "../handler-client/tsconfig.build.json" }, - { "path": "../handler-graphql/tsconfig.build.json" } + { "path": "../handler-graphql/tsconfig.build.json" }, + { "path": "../api-admin-users/tsconfig.build.json" }, + { "path": "../api-apw/tsconfig.build.json" }, + { "path": "../api-audit-logs/tsconfig.build.json" }, + { "path": "../api-headless-cms-aco/tsconfig.build.json" }, + { "path": "../api-headless-cms-tasks/tsconfig.build.json" }, + { "path": "../api-page-builder-import-export/tsconfig.build.json" }, + { "path": "../api-page-builder-import-export-so-ddb/tsconfig.build.json" }, + { "path": "../api-record-locking/tsconfig.build.json" }, + { "path": "../api-wcp/tsconfig.build.json" }, + { "path": "../api-websockets/tsconfig.build.json" }, + { "path": "../handler/tsconfig.build.json" }, + { "path": "../handler-aws/tsconfig.build.json" }, + { "path": "../plugins/tsconfig.build.json" }, + { "path": "../tasks/tsconfig.build.json" } ], "compilerOptions": { "rootDir": "./src", diff --git a/packages/api-serverless-cms/tsconfig.json b/packages/api-serverless-cms/tsconfig.json index 46fc3804613..6cc8b697587 100644 --- a/packages/api-serverless-cms/tsconfig.json +++ b/packages/api-serverless-cms/tsconfig.json @@ -16,7 +16,21 @@ { "path": "../api-tenancy" }, { "path": "../error" }, { "path": "../handler-client" }, - { "path": "../handler-graphql" } + { "path": "../handler-graphql" }, + { "path": "../api-admin-users" }, + { "path": "../api-apw" }, + { "path": "../api-audit-logs" }, + { "path": "../api-headless-cms-aco" }, + { "path": "../api-headless-cms-tasks" }, + { "path": "../api-page-builder-import-export" }, + { "path": "../api-page-builder-import-export-so-ddb" }, + { "path": "../api-record-locking" }, + { "path": "../api-wcp" }, + { "path": "../api-websockets" }, + { "path": "../handler" }, + { "path": "../handler-aws" }, + { "path": "../plugins" }, + { "path": "../tasks" } ], "compilerOptions": { "rootDirs": ["./src", "./__tests__"], @@ -54,7 +68,39 @@ "@webiny/handler-client/*": ["../handler-client/src/*"], "@webiny/handler-client": ["../handler-client/src"], "@webiny/handler-graphql/*": ["../handler-graphql/src/*"], - "@webiny/handler-graphql": ["../handler-graphql/src"] + "@webiny/handler-graphql": ["../handler-graphql/src"], + "@webiny/api-admin-users/*": ["../api-admin-users/src/*"], + "@webiny/api-admin-users": ["../api-admin-users/src"], + "@webiny/api-apw/*": ["../api-apw/src/*"], + "@webiny/api-apw": ["../api-apw/src"], + "@webiny/api-audit-logs/*": ["../api-audit-logs/src/*"], + "@webiny/api-audit-logs": ["../api-audit-logs/src"], + "@webiny/api-headless-cms-aco/*": ["../api-headless-cms-aco/src/*"], + "@webiny/api-headless-cms-aco": ["../api-headless-cms-aco/src"], + "@webiny/api-headless-cms-tasks/*": ["../api-headless-cms-tasks/src/*"], + "@webiny/api-headless-cms-tasks": ["../api-headless-cms-tasks/src"], + "@webiny/api-page-builder-import-export/*": ["../api-page-builder-import-export/src/*"], + "@webiny/api-page-builder-import-export": ["../api-page-builder-import-export/src"], + "@webiny/api-page-builder-import-export-so-ddb/*": [ + "../api-page-builder-import-export-so-ddb/src/*" + ], + "@webiny/api-page-builder-import-export-so-ddb": [ + "../api-page-builder-import-export-so-ddb/src" + ], + "@webiny/api-record-locking/*": ["../api-record-locking/src/*"], + "@webiny/api-record-locking": ["../api-record-locking/src"], + "@webiny/api-wcp/*": ["../api-wcp/src/*"], + "@webiny/api-wcp": ["../api-wcp/src"], + "@webiny/api-websockets/*": ["../api-websockets/src/*"], + "@webiny/api-websockets": ["../api-websockets/src"], + "@webiny/handler/*": ["../handler/src/*"], + "@webiny/handler": ["../handler/src"], + "@webiny/handler-aws/*": ["../handler-aws/src/*"], + "@webiny/handler-aws": ["../handler-aws/src"], + "@webiny/plugins/*": ["../plugins/src/*"], + "@webiny/plugins": ["../plugins/src"], + "@webiny/tasks/*": ["../tasks/src/*"], + "@webiny/tasks": ["../tasks/src"] }, "baseUrl": "." } diff --git a/packages/handler-graphql/src/createGraphQLHandler.ts b/packages/handler-graphql/src/createGraphQLHandler.ts index ec2c021f2e7..ca9327ca19e 100644 --- a/packages/handler-graphql/src/createGraphQLHandler.ts +++ b/packages/handler-graphql/src/createGraphQLHandler.ts @@ -1,9 +1,8 @@ import { boolean } from "boolean"; import { GraphQLSchema } from "graphql"; -import { RoutePlugin } from "@webiny/handler"; +import { Context, RoutePlugin } from "@webiny/handler"; import WebinyError from "@webiny/error"; -import { PluginCollection } from "@webiny/plugins/types"; -import { Context } from "@webiny/handler"; +import { Plugin } from "@webiny/plugins/types"; import { GraphQLRequestBody, HandlerGraphQLOptions } from "./types"; import { createGraphQLSchema, getSchemaPlugins } from "./createGraphQLSchema"; import debugPlugins from "./debugPlugins"; @@ -59,7 +58,7 @@ const formatErrorPayload = (error: Error): string => { }); }; -export default (options: HandlerGraphQLOptions = {}): PluginCollection => { +export default (options: HandlerGraphQLOptions = {}): Plugin[] => { let schema: GraphQLSchema | undefined = undefined; let cacheKey: string | undefined = undefined; diff --git a/packages/handler-graphql/src/index.ts b/packages/handler-graphql/src/index.ts index 8b84f0f0d06..827d4a35565 100644 --- a/packages/handler-graphql/src/index.ts +++ b/packages/handler-graphql/src/index.ts @@ -1,3 +1,4 @@ +import { Plugin } from "@webiny/plugins/types"; import { HandlerGraphQLOptions } from "./types"; import createGraphQLHandler from "./createGraphQLHandler"; @@ -9,4 +10,6 @@ export * from "./processRequestBody"; export * from "./createResolverDecorator"; export * from "./ResolverDecoration"; -export default (options: HandlerGraphQLOptions = {}) => [createGraphQLHandler(options)]; +export default (options: HandlerGraphQLOptions = {}): Plugin[] => { + return createGraphQLHandler(options); +}; diff --git a/yarn.lock b/yarn.lock index 188ed9835e4..5c7c046403b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15045,21 +15045,36 @@ __metadata: "@babel/preset-typescript": ^7.23.3 "@webiny/api": 0.0.0 "@webiny/api-aco": 0.0.0 + "@webiny/api-admin-users": 0.0.0 + "@webiny/api-apw": 0.0.0 + "@webiny/api-audit-logs": 0.0.0 "@webiny/api-file-manager": 0.0.0 "@webiny/api-form-builder": 0.0.0 "@webiny/api-headless-cms": 0.0.0 + "@webiny/api-headless-cms-aco": 0.0.0 + "@webiny/api-headless-cms-tasks": 0.0.0 "@webiny/api-i18n": 0.0.0 "@webiny/api-i18n-content": 0.0.0 "@webiny/api-page-builder": 0.0.0 "@webiny/api-page-builder-aco": 0.0.0 + "@webiny/api-page-builder-import-export": 0.0.0 + "@webiny/api-page-builder-import-export-so-ddb": 0.0.0 "@webiny/api-prerendering-service": 0.0.0 + "@webiny/api-record-locking": 0.0.0 "@webiny/api-security": 0.0.0 "@webiny/api-tenancy": 0.0.0 + "@webiny/api-wcp": 0.0.0 + "@webiny/api-websockets": 0.0.0 "@webiny/cli": 0.0.0 "@webiny/error": 0.0.0 + "@webiny/handler": 0.0.0 + "@webiny/handler-aws": 0.0.0 "@webiny/handler-client": 0.0.0 "@webiny/handler-graphql": 0.0.0 + "@webiny/plugins": 0.0.0 "@webiny/project-utils": 0.0.0 + "@webiny/tasks": 0.0.0 + graphql: ^15.8.0 rimraf: ^5.0.5 ttypescript: ^1.5.13 typescript: 4.9.5