Repository: saleor/apps
Pull request #1888: Tests for config repo
Author: login: lkostrowski
Labels: name: skip changeset
description: Attach this label to PRs which does not need changes description for the release notes.
name: App Payment: Stripe
Created: 2025-04-28T07:46:54Z | Merged: 2025-04-28T07:59:45Z

## Scope of the PR

<!-- Describe briefly changed made in this PR -->

## Related issues

<!-- If any, mention issues that are connected with this PR -->

## Checklist

- [ ] I added changesets and [read good practices](/.changeset/README.md).



Diff:
diff --git a/apps/stripe/src/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks.ts b/apps/stripe/src/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks.ts
new file mode 100644
index 000000000..f0fed6a4a
--- /dev/null
+++ b/apps/stripe/src/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks.ts
@@ -0,0 +1,37 @@
+import {
+  mockedConfigurationId,
+  mockedSaleorAppId,
+  mockedSaleorChannelId,
+} from "@/__tests__/mocks/constants";
+import { mockedSaleorApiUrl } from "@/__tests__/mocks/saleor-api-url";
+
+const mockedStripeConfig = {
+  configName: "tasdafsdf",
+  createdAt: "2025-04-25T09:19:13.402Z",
+  stripeWhId: "we_1RHiPdKFxIUko8m01KAnXiRQ",
+  configId: mockedConfigurationId,
+  stripePk:
+    "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
+  modifiedAt: "2025-04-25T09:19:13.402Z",
+  stripeWhSecret: "whsec_ZOsiN376Ahfo0N8lWg7PYXNGpnDXShS5",
+  SK: `CONFIG_ID#${mockedConfigurationId}`,
+  stripeRk:
+    "rk_test_51Ng2yKKFxIUko8m0eaadRYweTPBGnnBA58rpt5JQ7Y7VqSBnQu39JHWoqMfd5lSxH9OH44Bm5NMOQkbzpaMdjD3v00VsW1DGyx",
+  PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+  _et: "StripeConfig",
+};
+
+const mockedMapping = {
+  createdAt: "2025-04-25T10:26:30.219Z",
+  configId: mockedConfigurationId,
+  modifiedAt: "2025-04-25T10:26:30.219Z",
+  SK: `CHANNEL_ID#${mockedSaleorChannelId}`,
+  PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+  channelId: mockedSaleorChannelId,
+  _et: "ChannelConfigMapping",
+};
+
+export const mockedDynamoConfigItems = {
+  mockedStripeConfig,
+  mockedMapping,
+};
diff --git a/apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts b/apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts
index 84ae3cf29..91622ce33 100644
--- a/apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts
+++ b/apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts
@@ -1,23 +1,386 @@
-import { describe, it } from "vitest";
+import {
+  DynamoDBDocumentClient,
+  GetCommand,
+  PutCommand,
+  QueryCommand,
+} from "@aws-sdk/lib-dynamodb";
+import { mockClient } from "aws-sdk-client-mock";
+import { beforeEach, describe, expect, it } from "vitest";
+
+import {
+  mockedConfigurationId,
+  mockedSaleorAppId,
+  mockedSaleorChannelId,
+} from "@/__tests__/mocks/constants";
+import { mockedStripeConfig } from "@/__tests__/mocks/mock-stripe-config";
+import { mockedSaleorApiUrl } from "@/__tests__/mocks/saleor-api-url";
+import { AppRootConfig } from "@/modules/app-config/domain/app-root-config";
+import { StripeConfig } from "@/modules/app-config/domain/stripe-config";
+import { AppConfigRepoError } from "@/modules/app-config/repositories/app-config-repo";
+import { mockedDynamoConfigItems } from "@/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks";
+import { DynamoDbChannelConfigMapping } from "@/modules/app-config/repositories/dynamodb/channel-config-mapping-db-model";
+import { DynamodbAppConfigRepo } from "@/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo";
+import { DynamoDbStripeConfig } from "@/modules/app-config/repositories/dynamodb/stripe-config-db-model";
+import { DynamoMainTable } from "@/modules/dynamodb/dynamo-main-table";
 
 describe("DynamodbAppConfigRepo", () => {
+  let repo: DynamodbAppConfigRepo;
+
+  const mockDocumentClient = mockClient(DynamoDBDocumentClient);
+
+  beforeEach(() => {
+    mockDocumentClient.reset();
+
+    const table = DynamoMainTable.create({
+      // @ts-expect-error mocking DynamoDBDocumentClient
+      documentClient: mockDocumentClient,
+      tableName: "stripe-test-table",
+    });
+
+    const channelMappingEntity = DynamoDbChannelConfigMapping.createEntity(table);
+    const stripeConfigEntity = DynamoDbStripeConfig.createEntity(table);
+
+    repo = new DynamodbAppConfigRepo({
+      entities: {
+        stripeConfig: stripeConfigEntity,
+        channelConfigMapping: channelMappingEntity,
+      },
+    });
+  });
+
   describe("getRootConfig", () => {
-    it.todo("Returns valid RootAppConfig with entries from database");
-    it.todo("Returns FailureFetchingConfig if DB connection fails");
+    it("Returns valid RootAppConfig with entries from database", async () => {
+      mockDocumentClient
+        .on(QueryCommand, {
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CONFIG_ID#",
+            ":c1_1": "StripeConfig",
+          },
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedStripeConfig,
+            {
+              ...mockedDynamoConfigItems.mockedStripeConfig,
+              configId: "another-config-id",
+            },
+          ],
+        });
+
+      mockDocumentClient
+        .on(QueryCommand, {
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CHANNEL_ID#",
+            ":c1_1": "ChannelConfigMapping",
+          },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedMapping,
+            {
+              ...mockedDynamoConfigItems.mockedMapping,
+              channelId: "another-channel-id",
+            },
+          ],
+        });
+
+      const result = await repo.getRootConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeInstanceOf(AppRootConfig);
+      expect(result._unsafeUnwrap()).toMatchInlineSnapshot(`
+        AppRootConfig {
+          "chanelConfigMapping": {
+            "Q2hhbm5lbDox": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+            "another-channel-id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          },
+          "stripeConfigsById": {
+            "81f323bd-91e2-4838-ab6e-5affd81ffc3b": StripeConfig {
+              "id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+              "name": "tasdafsdf",
+              "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
+              "restrictedKey": "rk_test_51Ng2yKKFxIUko8m0eaadRYweTPBGnnBA58rpt5JQ7Y7VqSBnQu39JHWoqMfd5lSxH9OH44Bm5NMOQkbzpaMdjD3v00VsW1DGyx",
+              "webhookId": "we_1RHiPdKFxIUko8m01KAnXiRQ",
+              "webhookSecret": "whsec_ZOsiN376Ahfo0N8lWg7PYXNGpnDXShS5",
+            },
+            "another-config-id": StripeConfig {
+              "id": "another-config-id",
+              "name": "tasdafsdf",
+              "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
+              "restrictedKey": "rk_test_51Ng2yKKFxIUko8m0eaadRYweTPBGnnBA58rpt5JQ7Y7VqSBnQu39JHWoqMfd5lSxH9OH44Bm5NMOQkbzpaMdjD3v00VsW1DGyx",
+              "webhookId": "we_1RHiPdKFxIUko8m01KAnXiRQ",
+              "webhookSecret": "whsec_ZOsiN376Ahfo0N8lWg7PYXNGpnDXShS5",
+            },
+          },
+        }
+      `);
+    });
+
+    it("Returns FailureFetchingConfig if DB connection fails", async () => {
+      const result = await repo.getRootConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result.isErr()).toBe(true);
+
+      if (result.isErr()) {
+        expect(result.error).toBeInstanceOf(AppConfigRepoError.FailureFetchingConfig);
+      }
+    });
   });
 
   describe("getStripeConfig", () => {
-    it.todo("Returns null if entry not found in DB");
-    it.todo("Returns valid StripeConfig if entry found in DB");
-    it.todo("Returns FailureFetchingConfig if DB connection fails");
+    it("Returns null if entry not found in DB", async () => {
+      mockDocumentClient.on(GetCommand, {}).resolvesOnce({});
+
+      const result = await repo.getStripeConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+        configId: mockedConfigurationId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeNull();
+    });
+
+    it("Returns valid StripeConfig if entry found in DB", async () => {
+      mockDocumentClient.on(GetCommand, {}).resolvesOnce({
+        Item: mockedDynamoConfigItems.mockedStripeConfig,
+      });
+
+      const result = await repo.getStripeConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+        configId: mockedConfigurationId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeInstanceOf(StripeConfig);
+      expect(result._unsafeUnwrap()).toMatchInlineSnapshot(`
+        StripeConfig {
+          "id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          "name": "tasdafsdf",
+          "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
+          "restrictedKey": "rk_test_51Ng2yKKFxIUko8m0eaadRYweTPBGnnBA58rpt5JQ7Y7VqSBnQu39JHWoqMfd5lSxH9OH44Bm5NMOQkbzpaMdjD3v00VsW1DGyx",
+          "webhookId": "we_1RHiPdKFxIUko8m01KAnXiRQ",
+          "webhookSecret": "whsec_ZOsiN376Ahfo0N8lWg7PYXNGpnDXShS5",
+        }
+      `);
+    });
+
+    it("Returns valid StripeConfig when queried by channelId", async () => {
+      mockDocumentClient
+        .on(GetCommand, {
+          Key: {
+            PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+            SK: `CONFIG_ID#${mockedConfigurationId}`,
+          },
+        })
+        .resolvesOnce({
+          Item: mockedDynamoConfigItems.mockedStripeConfig,
+        });
+
+      mockDocumentClient
+        .on(GetCommand, {
+          Key: {
+            PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+            SK: `CHANNEL_ID#${mockedSaleorChannelId}`,
+          },
+        })
+        .resolvesOnce({
+          Item: mockedDynamoConfigItems.mockedMapping,
+        });
+
+      const result = await repo.getStripeConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+        channelId: mockedSaleorChannelId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeInstanceOf(StripeConfig);
+      expect(result._unsafeUnwrap()).toMatchInlineSnapshot(`
+        StripeConfig {
+          "id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          "name": "tasdafsdf",
+          "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
+          "restrictedKey": "rk_test_51Ng2yKKFxIUko8m0eaadRYweTPBGnnBA58rpt5JQ7Y7VqSBnQu39JHWoqMfd5lSxH9OH44Bm5NMOQkbzpaMdjD3v00VsW1DGyx",
+          "webhookId": "we_1RHiPdKFxIUko8m01KAnXiRQ",
+          "webhookSecret": "whsec_ZOsiN376Ahfo0N8lWg7PYXNGpnDXShS5",
+        }
+      `);
+    });
+
+    it("Returns null when channelId mapping not found", async () => {
+      mockDocumentClient
+        .on(GetCommand, {
+          Key: {
+            PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+            SK: `CHANNEL_ID#${mockedSaleorChannelId}`,
+          },
+        })
+        .resolvesOnce({});
+
+      const result = await repo.getStripeConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+        channelId: mockedSaleorChannelId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeNull();
+    });
+
+    it("Returns FailureFetchingConfig if DB connection fails", async () => {
+      mockDocumentClient
+        .on(GetCommand, {
+          Key: {
+            PK: `${mockedSaleorApiUrl}#${mockedSaleorAppId}`,
+            SK: `CHANNEL_ID#${mockedSaleorChannelId}`,
+          },
+        })
+        .resolvesOnce({
+          $metadata: {
+            httpStatusCode: 500,
+          },
+        });
+
+      const result = await repo.getStripeConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+        configId: mockedConfigurationId,
+      });
+
+      expect(result._unsafeUnwrapErr()).toBeInstanceOf(AppConfigRepoError.FailureFetchingConfig);
+    });
   });
 
   describe("saveStripeConfig", () => {
-    it.todo("Saves config to DB by calling method on entity");
-    it.todo("Returns FailureSavingConfig if DB connection fails");
+    it("Saves config to DB by calling method on entity", async () => {
+      mockDocumentClient.on(PutCommand, {}).resolvesOnce({
+        $metadata: {
+          httpStatusCode: 200,
+        },
+      });
+
+      const result = await repo.saveStripeConfig({
+        config: mockedStripeConfig,
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result.isOk()).toBe(true);
+
+      /**
+       * Assert args that are sent to DynamoDB
+       */
+      // @ts-expect-error - Item is not defined on mock
+      expect(mockDocumentClient.calls()[0].args[0].input.Item).toMatchInlineSnapshot(
+        {
+          createdAt: expect.any(String),
+          modifiedAt: expect.any(String),
+        },
+        `
+        {
+          "PK": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+          "SK": "CONFIG_ID#81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          "_et": "StripeConfig",
+          "configId": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          "configName": "config-name",
+          "createdAt": Any<String>,
+          "modifiedAt": Any<String>,
+          "stripePk": "pk_live_1",
+          "stripeRk": "rk_live_AAAAABBBBCCCCCEEEEEEEFFFFFGGGGG",
+          "stripeWhId": "wh_123456789",
+          "stripeWhSecret": "whsec_XYZ",
+        }
+      `,
+      );
+    });
+
+    it("Returns FailureSavingConfig if DB connection fails", async () => {
+      const result = await repo.saveStripeConfig({
+        config: mockedStripeConfig,
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result.isErr()).toBe(true);
+
+      if (result.isErr()) {
+        expect(result.error).toBeInstanceOf(AppConfigRepoError.FailureSavingConfig);
+      }
+    });
   });
+
   describe("updateMapping", () => {
-    it.todo("Saves config to DB by calling method on entity");
-    it.todo("Returns FailureSavingConfig if DB connection fails");
+    it("Saves config to DB by calling method on entity", async () => {
+      mockDocumentClient.on(PutCommand, {}).resolvesOnce({
+        $metadata: {
+          httpStatusCode: 200,
+        },
+      });
+
+      const result = await repo.updateMapping(
+        {
+          saleorApiUrl: mockedSaleorApiUrl,
+          appId: mockedSaleorAppId,
+        },
+        {
+          configId: mockedConfigurationId,
+          channelId: mockedSaleorChannelId,
+        },
+      );
+
+      expect(result.isOk()).toBe(true);
+
+      /**
+       * Assert args that are sent to DynamoDB
+       */
+      // @ts-expect-error - Item is not defined on mock
+      expect(mockDocumentClient.calls()[0].args[0].input.Item).toMatchInlineSnapshot(
+        {
+          createdAt: expect.any(String),
+          modifiedAt: expect.any(String),
+        },
+        `
+        {
+          "PK": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+          "SK": "CHANNEL_ID#Q2hhbm5lbDox",
+          "_et": "ChannelConfigMapping",
+          "channelId": "Q2hhbm5lbDox",
+          "configId": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          "createdAt": Any<String>,
+          "modifiedAt": Any<String>,
+        }
+      `,
+      );
+    });
+
+    it("Returns FailureSavingConfig if DB connection fails", async () => {
+      const result = await repo.updateMapping(
+        {
+          saleorApiUrl: mockedSaleorApiUrl,
+          appId: mockedSaleorAppId,
+        },
+        {
+          configId: mockedConfigurationId,
+          channelId: mockedSaleorChannelId,
+        },
+      );
+
+      expect(result.isErr()).toBe(true);
+
+      if (result.isErr()) {
+        expect(result.error).toBeInstanceOf(AppConfigRepoError.FailureSavingConfig);
+      }
+    });
   });
 });


Reviews:
user: login: kzuraw
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T07:58:07Z
user: login: lkostrowski
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-28T07:59:39Z

Review comments:
diff_hunk: @@ -1,23 +1,386 @@
-import { describe, it } from "vitest";
+import {
+  DynamoDBDocumentClient,
+  GetCommand,
+  PutCommand,
+  QueryCommand,
+} from "@aws-sdk/lib-dynamodb";
+import { mockClient } from "aws-sdk-client-mock";
+import { beforeEach, describe, expect, it } from "vitest";
+
+import {
+  mockedConfigurationId,
+  mockedSaleorAppId,
+  mockedSaleorChannelId,
+} from "@/__tests__/mocks/constants";
+import { mockedStripeConfig } from "@/__tests__/mocks/mock-stripe-config";
+import { mockedSaleorApiUrl } from "@/__tests__/mocks/saleor-api-url";
+import { AppRootConfig } from "@/modules/app-config/domain/app-root-config";
+import { StripeConfig } from "@/modules/app-config/domain/stripe-config";
+import { AppConfigRepoError } from "@/modules/app-config/repositories/app-config-repo";
+import { mockedDynamoConfigItems } from "@/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks";
+import { DynamoDbChannelConfigMapping } from "@/modules/app-config/repositories/dynamodb/channel-config-mapping-db-model";
+import { DynamodbAppConfigRepo } from "@/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo";
+import { DynamoDbStripeConfig } from "@/modules/app-config/repositories/dynamodb/stripe-config-db-model";
+import { DynamoMainTable } from "@/modules/dynamodb/dynamo-main-table";
 
 describe("DynamodbAppConfigRepo", () => {
+  let repo: DynamodbAppConfigRepo;
+
+  const mockDocumentClient = mockClient(DynamoDBDocumentClient);
+
+  beforeEach(() => {
+    mockDocumentClient.reset();
+
+    const table = DynamoMainTable.create({
+      // @ts-expect-error mocking DynamoDBDocumentClient
+      documentClient: mockDocumentClient,
+      tableName: "stripe-test-table",
+    });
+
+    const channelMappingEntity = DynamoDbChannelConfigMapping.createEntity(table);
+    const stripeConfigEntity = DynamoDbStripeConfig.createEntity(table);
+
+    repo = new DynamodbAppConfigRepo({
+      entities: {
+        stripeConfig: stripeConfigEntity,
+        channelConfigMapping: channelMappingEntity,
+      },
+    });
+  });
+
   describe("getRootConfig", () => {
-    it.todo("Returns valid RootAppConfig with entries from database");
-    it.todo("Returns FailureFetchingConfig if DB connection fails");
+    it("Returns valid RootAppConfig with entries from database", async () => {
+      mockDocumentClient
+        .on(QueryCommand, {
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CONFIG_ID#",
+            ":c1_1": "StripeConfig",
+          },
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedStripeConfig,
+            {
+              ...mockedDynamoConfigItems.mockedStripeConfig,
+              configId: "another-config-id",
+            },
+          ],
+        });
+
+      mockDocumentClient
+        .on(QueryCommand, {
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CHANNEL_ID#",
+            ":c1_1": "ChannelConfigMapping",
+          },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedMapping,
+            {
+              ...mockedDynamoConfigItems.mockedMapping,
+              channelId: "another-channel-id",
+            },
+          ],
+        });
+
+      const result = await repo.getRootConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeInstanceOf(AppRootConfig);
+      expect(result._unsafeUnwrap()).toMatchInlineSnapshot(`
+        AppRootConfig {
+          "chanelConfigMapping": {
+            "Q2hhbm5lbDox": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+            "another-channel-id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          },
+          "stripeConfigsById": {
+            "81f323bd-91e2-4838-ab6e-5affd81ffc3b": StripeConfig {
+              "id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+              "name": "tasdafsdf",
+              "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
path: apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts
user: login: kzuraw
body: **Suggestion:** maybe we can have here just `any<string>`?
created_at: 2025-04-28T07:56:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,23 +1,386 @@
-import { describe, it } from "vitest";
+import {
+  DynamoDBDocumentClient,
+  GetCommand,
+  PutCommand,
+  QueryCommand,
+} from "@aws-sdk/lib-dynamodb";
+import { mockClient } from "aws-sdk-client-mock";
+import { beforeEach, describe, expect, it } from "vitest";
+
+import {
+  mockedConfigurationId,
+  mockedSaleorAppId,
+  mockedSaleorChannelId,
+} from "@/__tests__/mocks/constants";
+import { mockedStripeConfig } from "@/__tests__/mocks/mock-stripe-config";
+import { mockedSaleorApiUrl } from "@/__tests__/mocks/saleor-api-url";
+import { AppRootConfig } from "@/modules/app-config/domain/app-root-config";
+import { StripeConfig } from "@/modules/app-config/domain/stripe-config";
+import { AppConfigRepoError } from "@/modules/app-config/repositories/app-config-repo";
+import { mockedDynamoConfigItems } from "@/modules/app-config/repositories/dynamodb/__tests__/app-config-dynamodb-mocks";
+import { DynamoDbChannelConfigMapping } from "@/modules/app-config/repositories/dynamodb/channel-config-mapping-db-model";
+import { DynamodbAppConfigRepo } from "@/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo";
+import { DynamoDbStripeConfig } from "@/modules/app-config/repositories/dynamodb/stripe-config-db-model";
+import { DynamoMainTable } from "@/modules/dynamodb/dynamo-main-table";
 
 describe("DynamodbAppConfigRepo", () => {
+  let repo: DynamodbAppConfigRepo;
+
+  const mockDocumentClient = mockClient(DynamoDBDocumentClient);
+
+  beforeEach(() => {
+    mockDocumentClient.reset();
+
+    const table = DynamoMainTable.create({
+      // @ts-expect-error mocking DynamoDBDocumentClient
+      documentClient: mockDocumentClient,
+      tableName: "stripe-test-table",
+    });
+
+    const channelMappingEntity = DynamoDbChannelConfigMapping.createEntity(table);
+    const stripeConfigEntity = DynamoDbStripeConfig.createEntity(table);
+
+    repo = new DynamodbAppConfigRepo({
+      entities: {
+        stripeConfig: stripeConfigEntity,
+        channelConfigMapping: channelMappingEntity,
+      },
+    });
+  });
+
   describe("getRootConfig", () => {
-    it.todo("Returns valid RootAppConfig with entries from database");
-    it.todo("Returns FailureFetchingConfig if DB connection fails");
+    it("Returns valid RootAppConfig with entries from database", async () => {
+      mockDocumentClient
+        .on(QueryCommand, {
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CONFIG_ID#",
+            ":c1_1": "StripeConfig",
+          },
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedStripeConfig,
+            {
+              ...mockedDynamoConfigItems.mockedStripeConfig,
+              configId: "another-config-id",
+            },
+          ],
+        });
+
+      mockDocumentClient
+        .on(QueryCommand, {
+          KeyConditionExpression: "(#c0_1 = :c0_1) AND (begins_with(#c0_2, :c0_2))",
+          FilterExpression: "#c1_1 = :c1_1",
+          ExpressionAttributeNames: { "#c0_1": "PK", "#c0_2": "SK", "#c1_1": "_et" },
+          ExpressionAttributeValues: {
+            ":c0_1": "https://foo.bar.saleor.cloud/graphql/#saleor-app-id",
+            ":c0_2": "CHANNEL_ID#",
+            ":c1_1": "ChannelConfigMapping",
+          },
+        })
+        .resolvesOnce({
+          Items: [
+            mockedDynamoConfigItems.mockedMapping,
+            {
+              ...mockedDynamoConfigItems.mockedMapping,
+              channelId: "another-channel-id",
+            },
+          ],
+        });
+
+      const result = await repo.getRootConfig({
+        saleorApiUrl: mockedSaleorApiUrl,
+        appId: mockedSaleorAppId,
+      });
+
+      expect(result._unsafeUnwrap()).toBeInstanceOf(AppRootConfig);
+      expect(result._unsafeUnwrap()).toMatchInlineSnapshot(`
+        AppRootConfig {
+          "chanelConfigMapping": {
+            "Q2hhbm5lbDox": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+            "another-channel-id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+          },
+          "stripeConfigsById": {
+            "81f323bd-91e2-4838-ab6e-5affd81ffc3b": StripeConfig {
+              "id": "81f323bd-91e2-4838-ab6e-5affd81ffc3b",
+              "name": "tasdafsdf",
+              "publishableKey": "pk_test_51Ng2yKKFxIUko8m0IUBO8GvarTlXcNIpAATM9cE7S2GaoFLsTAsn5avHHjfLFVKewqTFwMb2wqOP87CEbgwljzf200aXXm38oM",
path: apps/stripe/src/modules/app-config/repositories/dynamodb/dynamodb-app-config-repo.test.ts
user: login: lkostrowski
body: Why? These are mocks and are hardcoded in mocks, it will not change automatically
created_at: 2025-04-28T07:59:39Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-04-28T07:46:57Z
body: ###  ⚠️  No Changeset found

Latest commit: 8d05bdde4ce5ef73c5c80c43d974f744d668e5fc

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/saleor/apps/new/db-config-repo-tests?filename=.changeset/fast-papayas-live.md&value=---%0A%22saleor-app-payment-stripe%22%3A%20patch%0A---%0A%0ATests%20for%20config%20repo%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-04-28T07:46:58Z
body: [vc]: #6ALVEAd3EK+ttRriTxuSEQYFskLxMDtgjO11Bx9YJfI=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzYWxlb3ItYXBwLXNlYXJjaCIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3NlYXJjaCIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9zYWxlb3Jjb21tZXJjZS9zYWxlb3ItYXBwLXNlYXJjaC80RFVCZkZjN0toYnpxaDNhNWZEOVNZc3FadzNaIiwicHJldmlld1VybCI6InNhbGVvci1hcHAtc2VhcmNoLWdpdC1kYi1jb25maWctcmVwby10ZXN0cy1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InNhbGVvci1hcHAtc2VhcmNoLWdpdC1kYi1jb25maWctcmVwby10ZXN0cy1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6InNhbGVvci1hcHAta2xhdml5byIsInJvb3REaXJlY3RvcnkiOiJhcHBzL2tsYXZpeW8iLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc2FsZW9yY29tbWVyY2Uvc2FsZW9yLWFwcC1rbGF2aXlvL0Z6UkJMMzNDbjVuOFBZOVl4dG9TeWg3cnBoV1QiLCJwcmV2aWV3VXJsIjoic2FsZW9yLWFwcC1rbGF2aXlvLWdpdC1kYi1jb25maWctcmVwby10ZXN0cy1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InNhbGVvci1hcHAta2xhdml5by1naXQtZGItY29uZmlnLXJlcG8tdGVzdHMtc2FsZW9yY29tbWVyY2UudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJzYWxlb3ItYXBwLXByb2R1Y3RzLWZlZWQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9wcm9kdWN0cy1mZWVkIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NhbGVvcmNvbW1lcmNlL3NhbGVvci1hcHAtcHJvZHVjdHMtZmVlZC82bW1QNGd5UGZraUxDeEF3bVJzVzdFZlF5a1JSIiwicHJldmlld1VybCI6InNhbGVvci1hcHAtcHJvZHVjdHMtZmVlZC1naXQtZGItY29uZmlnLXJlLTFhYjc2NS1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6InNhbGVvci1hcHAtcHJvZHVjdHMtZmVlZC1naXQtZGItY29uZmlnLXJlLTFhYjc2NS1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6InNhbGVvci1hcHAtY21zIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvY21zIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NhbGVvcmNvbW1lcmNlL3NhbGVvci1hcHAtY21zL0UyOXp6TnQ0aDJIZ2FoQnBZRUpLSm5aenV2RVgiLCJwcmV2aWV3VXJsIjoic2FsZW9yLWFwcC1jbXMtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoic2FsZW9yLWFwcC1jbXMtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAifX0seyJuYW1lIjoic2FsZW9yLWFwcC1hdmF0YXgiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9hdmF0YXgiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc2FsZW9yY29tbWVyY2Uvc2FsZW9yLWFwcC1hdmF0YXgvQVo3RGRzOEU4ZUdCUzc5ZWdmZm8zVWYyaXRKNSIsInByZXZpZXdVcmwiOiJzYWxlb3ItYXBwLWF2YXRheC1naXQtZGItY29uZmlnLXJlcG8tdGVzdHMtc2FsZW9yY29tbWVyY2UudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzYWxlb3ItYXBwLWF2YXRheC1naXQtZGItY29uZmlnLXJlcG8tdGVzdHMtc2FsZW9yY29tbWVyY2UudmVyY2VsLmFwcCJ9fSx7Im5hbWUiOiJzYWxlb3ItYXBwLXNtdHAiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9zbXRwIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NhbGVvcmNvbW1lcmNlL3NhbGVvci1hcHAtc210cC9GYm5GRkV2UXNSZmVMMzFjNnFRSjNEU2pQNjJ2IiwicHJldmlld1VybCI6InNhbGVvci1hcHAtc210cC1naXQtZGItY29uZmlnLXJlcG8tdGVzdHMtc2FsZW9yY29tbWVyY2UudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJzYWxlb3ItYXBwLXNtdHAtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAifX0seyJuYW1lIjoic2FsZW9yLWFwcC1zZWdtZW50Iiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc2VnbWVudCIsImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9zYWxlb3Jjb21tZXJjZS9zYWxlb3ItYXBwLXNlZ21lbnQvM2pKdFprcXJzTFk0NG5CZlEzV3FTQjNDYmlKOCIsInByZXZpZXdVcmwiOiJzYWxlb3ItYXBwLXNlZ21lbnQtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoic2FsZW9yLWFwcC1zZWdtZW50LWdpdC1kYi1jb25maWctcmVwby10ZXN0cy1zYWxlb3Jjb21tZXJjZS52ZXJjZWwuYXBwIn19LHsibmFtZSI6ImFwcHMtc3RyaXBlIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc3RyaXBlIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3NhbGVvcmNvbW1lcmNlL2FwcHMtc3RyaXBlLzQxWW93d2pvTUtxRjhXb0pBVGRHMVZNTXl3OTUiLCJwcmV2aWV3VXJsIjoiYXBwcy1zdHJpcGUtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiYXBwcy1zdHJpcGUtZ2l0LWRiLWNvbmZpZy1yZXBvLXRlc3RzLXNhbGVvcmNvbW1lcmNlLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **apps-stripe** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/apps-stripe/41YowwjoMKqF8WoJATdG1VMMyw95)) | [Visit Preview](https://apps-stripe-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/apps-stripe-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-avatax** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-avatax/AZ7Dds8E8eGBS79egffo3Uf2itJ5)) | [Visit Preview](https://saleor-app-avatax-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-avatax-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-cms** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-cms/E29zzNt4h2HgahBpYEJKJnZzuvEX)) | [Visit Preview](https://saleor-app-cms-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-cms-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-klaviyo** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-klaviyo/FzRBL33Cn5n8PY9YxtoSyh7rphWT)) | [Visit Preview](https://saleor-app-klaviyo-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-klaviyo-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-products-feed** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-products-feed/6mmP4gyPfkiLCxAwmRsW7EfQykRR)) | [Visit Preview](https://saleor-app-products-feed-git-db-config-re-1ab765-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-products-feed-git-db-config-re-1ab765-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-search** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-search/4DUBfFc7Khbzqh3a5fD9SYsqZw3Z)) | [Visit Preview](https://saleor-app-search-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-search-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-segment** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-segment/3jJtZkqrsLY44nBfQ3WqSB3CbiJ8)) | [Visit Preview](https://saleor-app-segment-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-segment-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |
| **saleor-app-smtp** | ✅ Ready ([Inspect](https://vercel.com/saleorcommerce/saleor-app-smtp/FbnFFEvQsRfeL31c6qQJ3DSjP62v)) | [Visit Preview](https://saleor-app-smtp-git-db-config-repo-tests-saleorcommerce.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/saleor-app-smtp-git-db-config-repo-tests-saleorcommerce.vercel.app?via=pr-comment-feedback-link) | Apr 28, 2025 7:54am |









author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-28T07:49:18Z
body: ## [Codecov](https://app.codecov.io/gh/saleor/apps/pull/1888?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=saleor) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 27.98%. Comparing base [(`d7b0b49`)](https://app.codecov.io/gh/saleor/apps/commit/d7b0b49bcd70b6bd8678a24a6e936d2e3dd05819?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=saleor) to head [(`8d05bdd`)](https://app.codecov.io/gh/saleor/apps/commit/8d05bdde4ce5ef73c5c80c43d974f744d668e5fc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=saleor).
> Report is 1 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1888      +/-   ##
==========================================
+ Coverage   27.62%   27.98%   +0.35%     
==========================================
  Files         817      817              
  Lines       56125    56125              
  Branches     2115     2146      +31     
==========================================
+ Hits        15505    15704     +199     
+ Misses      40250    40051     -199     
  Partials      370      370              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/saleor/apps/pull/1888?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=saleor).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=saleor).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE