diff --git a/packages/api-file-manager/src/delivery/setupAssetDelivery.ts b/packages/api-file-manager/src/delivery/setupAssetDelivery.ts index 913bb069795..9a50159a9be 100644 --- a/packages/api-file-manager/src/delivery/setupAssetDelivery.ts +++ b/packages/api-file-manager/src/delivery/setupAssetDelivery.ts @@ -98,10 +98,12 @@ export const setupAssetDelivery = (params: AssetDeliveryParams) => { return false; } + const assetLocale = resolvedAsset.getLocale(); + request.headers = { ...request.headers, "x-tenant": resolvedAsset.getTenant(), - "x-i18n-locale": resolvedAsset.getLocale() + "x-i18n-locale": `default:${assetLocale};content:${assetLocale};` }; return; diff --git a/packages/api-page-builder-import-export/src/import/pages/ImportPagesProcessPages.ts b/packages/api-page-builder-import-export/src/import/pages/ImportPagesProcessPages.ts index 3160a4d8dc4..25618a0b87a 100644 --- a/packages/api-page-builder-import-export/src/import/pages/ImportPagesProcessPages.ts +++ b/packages/api-page-builder-import-export/src/import/pages/ImportPagesProcessPages.ts @@ -52,6 +52,7 @@ export class ImportPagesProcessPages { pageIdList.push(page.pid); done.push(item.key); } catch (ex) { + console.error(ex); await store.addErrorLog({ error: ex, message: `Could not import page: ${item.key}.`, diff --git a/packages/api-page-builder-import-export/src/import/utils/extractAndUploadZipFileContents.ts b/packages/api-page-builder-import-export/src/import/utils/extractAndUploadZipFileContents.ts index aa7f0bd85a3..0777b21c5a4 100644 --- a/packages/api-page-builder-import-export/src/import/utils/extractAndUploadZipFileContents.ts +++ b/packages/api-page-builder-import-export/src/import/utils/extractAndUploadZipFileContents.ts @@ -29,7 +29,7 @@ export async function extractAndUploadZipFileContents(zipFileUrl: string): Promi const response = await fetch(zipFileUrl); const readStream = response.body; if (!response.ok || !readStream) { - throw new WebinyError(`Unable to downloading file: "${zipFileUrl}"`, response.statusText); + throw new WebinyError(`Unable to download file: "${zipFileUrl}"`, response.statusText); } const uniquePath = uniqueId("IMPORTS/"); diff --git a/packages/tasks/__tests__/crud/definitions.test.ts b/packages/tasks/__tests__/crud/definitions.test.ts index 24ea24a20f8..07e21651a47 100644 --- a/packages/tasks/__tests__/crud/definitions.test.ts +++ b/packages/tasks/__tests__/crud/definitions.test.ts @@ -1,8 +1,8 @@ -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { createTaskDefinition } from "~/task"; describe("definitions crud", () => { - const handler = useHandler({ + const handler = useRawHandler({ plugins: [ createTaskDefinition({ id: "testDefinitionNumber1", diff --git a/packages/tasks/__tests__/crud/store.test.ts b/packages/tasks/__tests__/crud/store.test.ts index 37c0dda15a2..fa4859ae280 100644 --- a/packages/tasks/__tests__/crud/store.test.ts +++ b/packages/tasks/__tests__/crud/store.test.ts @@ -1,4 +1,4 @@ -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { createTaskDefinition } from "~/task"; import { ITask, TaskDataStatus } from "~/types"; import { NotFoundError } from "@webiny/handler-graphql"; @@ -6,7 +6,7 @@ import WebinyError from "@webiny/error"; import { createMockIdentity } from "~tests/mocks/identity"; describe("store crud", () => { - const handler = useHandler({ + const handler = useRawHandler({ plugins: [ createTaskDefinition({ id: "testDefinition", diff --git a/packages/tasks/__tests__/crud/trigger.test.ts b/packages/tasks/__tests__/crud/trigger.test.ts index 8b91613bf6a..8fc238d2e3e 100644 --- a/packages/tasks/__tests__/crud/trigger.test.ts +++ b/packages/tasks/__tests__/crud/trigger.test.ts @@ -1,4 +1,4 @@ -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { createMockTaskDefinitions } from "~tests/mocks/definition"; import { createMockIdentity } from "~tests/mocks/identity"; import { TaskDataStatus } from "~/types"; @@ -23,7 +23,7 @@ jest.mock("@webiny/aws-sdk/client-eventbridge", () => { }); describe("trigger crud", () => { - const handler = useHandler({ + const handler = useRawHandler({ plugins: [...createMockTaskDefinitions()] }); diff --git a/packages/tasks/__tests__/graphql/logs.test.ts b/packages/tasks/__tests__/graphql/logs.test.ts index 7d263f0b82b..0c0a2fb5809 100644 --- a/packages/tasks/__tests__/graphql/logs.test.ts +++ b/packages/tasks/__tests__/graphql/logs.test.ts @@ -1,11 +1,11 @@ import { useGraphQLHandler } from "~tests/helpers/useGraphQLHandler"; import { createMockTaskDefinitions } from "~tests/mocks/definition"; -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { ITaskLogItemType } from "~/types"; import { createMockIdentity } from "~tests/mocks/identity"; describe("graphql - logs", () => { - const contextHandler = useHandler({ + const contextHandler = useRawHandler({ plugins: [...createMockTaskDefinitions()] }); const handler = useGraphQLHandler({ diff --git a/packages/tasks/__tests__/graphql/tasks.test.ts b/packages/tasks/__tests__/graphql/tasks.test.ts index 8a4f81d5983..f2626628bee 100644 --- a/packages/tasks/__tests__/graphql/tasks.test.ts +++ b/packages/tasks/__tests__/graphql/tasks.test.ts @@ -1,11 +1,11 @@ import { useGraphQLHandler } from "~tests/helpers/useGraphQLHandler"; import { createMockTaskDefinitions } from "~tests/mocks/definition"; -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { TaskDataStatus } from "~/types"; import { createMockIdentity } from "~tests/mocks/identity"; describe("graphql - tasks", () => { - const contextHandler = useHandler({ + const contextHandler = useRawHandler({ plugins: [...createMockTaskDefinitions()] }); const handler = useGraphQLHandler({ diff --git a/packages/tasks/__tests__/helpers/tenancySecurity.ts b/packages/tasks/__tests__/helpers/tenancySecurity.ts index 4994176b331..3ae40e8860b 100644 --- a/packages/tasks/__tests__/helpers/tenancySecurity.ts +++ b/packages/tasks/__tests__/helpers/tenancySecurity.ts @@ -33,14 +33,23 @@ export const createTenancyAndSecurity = ({ const securityStorage = getStorageOps("security"); return [ + new ContextPlugin(async context => { + const original = context.wcp.canUseFeature; + context.wcp.canUseFeature = feature => { + if (feature === "multiTenancy") { + return true; + } + return original(feature); + }; + }), createTenancyContext({ storageOperations: tenancyStorage.storageOperations }), setupGraphQL ? createTenancyGraphQL() : null, createSecurityContext({ storageOperations: securityStorage.storageOperations }), setupGraphQL ? createSecurityGraphQL() : null, new ContextPlugin(context => { context.tenancy.setCurrentTenant({ - id: "root", - name: "Root", + id: context.request.headers["x-tenant"] || "root", + name: context.request.headers["x-tenant"] || "Root", webinyVersion: context.WEBINY_VERSION } as unknown as Tenant); diff --git a/packages/tasks/__tests__/helpers/useHandler.ts b/packages/tasks/__tests__/helpers/useRawHandler.ts similarity index 81% rename from packages/tasks/__tests__/helpers/useHandler.ts rename to packages/tasks/__tests__/helpers/useRawHandler.ts index b78e061c8ba..9c09bb60750 100644 --- a/packages/tasks/__tests__/helpers/useHandler.ts +++ b/packages/tasks/__tests__/helpers/useRawHandler.ts @@ -17,7 +17,7 @@ export interface UseHandlerParams { plugins?: PluginCollection; } -export const useHandler = (params?: UseHandlerParams) => { +export const useRawHandler = (params?: UseHandlerParams) => { const { plugins = [] } = params || {}; const cmsStorage = getStorageOps("cms"); const i18nStorage = getStorageOps("i18n"); @@ -49,8 +49,18 @@ export const useHandler = (params?: UseHandlerParam }); return { - handle: async () => { - return await handler({}, {} as LambdaContext); + handle: async (payload?: Record) => { + const headers = { + ["x-tenant"]: "root", + ...(payload?.headers || {}) + }; + return await handler( + { + ...payload, + headers + }, + {} as LambdaContext + ); } }; }; diff --git a/packages/tasks/__tests__/helpers/useTaskHandler.ts b/packages/tasks/__tests__/helpers/useTaskHandler.ts new file mode 100644 index 00000000000..7af49329c60 --- /dev/null +++ b/packages/tasks/__tests__/helpers/useTaskHandler.ts @@ -0,0 +1,57 @@ +import { createHandler } from "~/handler"; +import { createWcpContext } from "@webiny/api-wcp"; +import { createTenancyAndSecurity } from "~tests/helpers/tenancySecurity"; +import { createDummyLocales, createIdentity, createPermissions } from "~tests/helpers/helpers"; +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 { createBackgroundTaskContext } from "~/context"; +import { createRawEventHandler } from "@webiny/handler-aws"; +import { getStorageOps } from "@webiny/project-utils/testing/environment"; +import { HeadlessCmsStorageOperations } from "@webiny/api-headless-cms/types"; +import { PluginCollection } from "@webiny/plugins/types"; +import { LambdaContext } from "@webiny/handler-aws/types"; +import { ITaskRawEvent } from "~/handler/types"; + +export interface UseTaskHandlerParams { + plugins?: PluginCollection; +} + +export const useTaskHandler = (params?: UseTaskHandlerParams) => { + const { plugins = [] } = params || {}; + const cmsStorage = getStorageOps("cms"); + const i18nStorage = getStorageOps("i18n"); + + const handler = createHandler({ + plugins: [ + createWcpContext(), + ...cmsStorage.plugins, + ...createTenancyAndSecurity({ + setupGraphQL: false, + permissions: createPermissions(), + identity: createIdentity() + }), + i18nContext(), + i18nStorage.storageOperations, + createDummyLocales(), + mockLocalesPlugins(), + createHeadlessCmsContext({ + storageOperations: cmsStorage.storageOperations + }), + createHeadlessCmsGraphQL(), + graphQLHandlerPlugins(), + createBackgroundTaskContext(), + createRawEventHandler(async ({ context }) => { + return context; + }), + ...plugins + ] + }); + + return { + handle: async (payload: ITaskRawEvent) => { + return await handler(payload, {} as LambdaContext); + } + }; +}; diff --git a/packages/tasks/__tests__/live/context.ts b/packages/tasks/__tests__/live/context.ts index cf60e28ca75..b96f293a7c7 100644 --- a/packages/tasks/__tests__/live/context.ts +++ b/packages/tasks/__tests__/live/context.ts @@ -1,29 +1,30 @@ import { PluginCollection } from "@webiny/plugins/types"; -import { useHandler } from "~tests/helpers/useHandler"; +import { useRawHandler } from "~tests/helpers/useRawHandler"; import { Context } from "~tests/types"; export interface CreateLiveContextParams { plugins?: PluginCollection; - handler?: ReturnType>; + handler?: ReturnType>; } export const createLiveContext = async ( - params?: CreateLiveContextParams + params?: CreateLiveContextParams, + payload?: Record ) => { if (params?.handler) { - return params.handler.handle(); + return params.handler.handle(payload); } - const handler = useHandler({ + const handler = useRawHandler({ plugins: [...(params?.plugins || [])] }); - return handler.handle(); + return handler.handle(payload); }; export const createLiveContextFactory = ( params?: CreateLiveContextParams ) => { - return () => { - return createLiveContext(params); + return (payload: Record = {}) => { + return createLiveContext(params, payload); }; }; diff --git a/packages/tasks/__tests__/runner/taskTenantAndLocale.test.ts b/packages/tasks/__tests__/runner/taskTenantAndLocale.test.ts new file mode 100644 index 00000000000..221c8995bc5 --- /dev/null +++ b/packages/tasks/__tests__/runner/taskTenantAndLocale.test.ts @@ -0,0 +1,75 @@ +import { createMockEvent } from "~tests/mocks"; +import { createLiveContextFactory } from "~tests/live"; +import { createTaskDefinition } from "~/task"; +import { useTaskHandler } from "~tests/helpers/useTaskHandler"; + +const taskDefinition = createTaskDefinition({ + id: "taskRunnerTask", + title: "Task Runner Task", + maxIterations: 2, + run: async ({ response, context }) => { + return response.done("Task is done!", { + tenant: context.tenancy.getCurrentTenant().id, + locale: context.cms.getLocale().code, + defaultLocale: context.i18n.getCurrentLocale("default")!.code, + contentLocale: context.i18n.getCurrentLocale("content")!.code + }); + } +}); + +const defaults = { + tenant: "aCustomTenantId", + locale: "de-DE" +}; + +describe("task tenant and locale", () => { + it("should properly set the tenant and locale", async () => { + const contextFactory = createLiveContextFactory({ + plugins: [taskDefinition] + }); + + const context = await contextFactory({ + headers: { + ["x-tenant"]: defaults.tenant, + ["x-webiny-cms-endpoint"]: "manage", + ["x-webiny-cms-locale"]: defaults.locale, + ["x-i18n-locale"]: defaults.locale, + ["accept-language"]: defaults.locale + } + }); + + const task = await context.tasks.createTask({ + definitionId: taskDefinition.id, + input: {}, + name: "My task name" + }); + + const { handle } = useTaskHandler({ + plugins: [taskDefinition] + }); + + const result = await handle( + createMockEvent({ + webinyTaskId: task.id, + webinyTaskDefinitionId: taskDefinition.id, + tenant: defaults.tenant, + locale: defaults.locale + }) + ); + + expect(result).toEqual({ + status: "done", + webinyTaskId: task.id, + webinyTaskDefinitionId: taskDefinition.id, + tenant: "aCustomTenantId", + locale: "de-DE", + message: "Task is done!", + output: { + tenant: "aCustomTenantId", + locale: "de-DE", + defaultLocale: "de-DE", + contentLocale: "de-DE" + } + }); + }); +}); diff --git a/packages/tasks/src/handler/index.ts b/packages/tasks/src/handler/index.ts index ffb26daade9..ad104c3eb11 100644 --- a/packages/tasks/src/handler/index.ts +++ b/packages/tasks/src/handler/index.ts @@ -71,7 +71,9 @@ export const createHandler = (params: HandlerParams): HandlerCallable => { headers: { ["x-tenant"]: event.tenant, ["x-webiny-cms-endpoint"]: event.endpoint, - ["x-webiny-cms-locale"]: event.locale + ["x-webiny-cms-locale"]: event.locale, + ["x-i18n-locale"]: event.locale, + ["accept-language"]: event.locale } } }); diff --git a/packages/tasks/src/runner/TaskControl.ts b/packages/tasks/src/runner/TaskControl.ts index ef8241c1a64..d7036b2be04 100644 --- a/packages/tasks/src/runner/TaskControl.ts +++ b/packages/tasks/src/runner/TaskControl.ts @@ -38,6 +38,7 @@ export class TaskControl implements ITaskControl { let task: ITask; try { task = await this.getTask(taskId); + this.context.security.setIdentity(task.createdBy); } catch (error) { return this.response.error({ error @@ -111,8 +112,6 @@ export class TaskControl implements ITaskControl { } try { - this.context.security.setIdentity(task.createdBy); - const result = await manager.run(definition); await this.runEvents(result, definition, task);