Skip to content

Commit

Permalink
Merge pull request #2 from composable-com/dev
Browse files Browse the repository at this point in the history
Additional Unit Tests
  • Loading branch information
dannytlake authored Sep 26, 2023
2 parents a17d82e + 5b87587 commit c14936b
Show file tree
Hide file tree
Showing 14 changed files with 613 additions and 68 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Orium

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
96 changes: 79 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,84 @@
<p align="center">
<a href="https://commercetools.com/">
<img alt="commercetools logo" src="https://unpkg.com/@commercetools-frontend/assets/logos/commercetools_primary-logo_horizontal_RGB.png">
</a></br>
<b>Connect Application Starter in TypeScript</b>
</p>
# commercetools Fluent Commerce Connector

This is the `starter-typescript` template to develop [connect applications](https://marketplace.commercetools.com/) in TypeScript.
The Fluent Commerce commercetools connector was created by [Orium](https://orium.com/), and provides the following features:
- Ability to capture customer order events from commercetools to Fluent, including payment transaction details
- Ability to initially load all products from commercetools to Fluent Commerce when the connector is installed
- Ability to continuously synchronize products from commercetools to Fluent Commerce in near real-time

## Instructions
## Overview

Use `create-connect-app` cli with `starter-typescript` as `template` value to download this template repository to build the integration application , folder structure needs to be followed to ensure certification & deployment from commercetools connect team as stated [here](https://github.com/commercetools/connect-application-kit#readme)
The application receives events (like product publication or order creation) from commercetools, processes them, and then creates corresponding entities in FluentCommerce.

## Architecture principles for building an connect application
This is achieved through a POST endpoint which receives the event, processes it, and depending on the event type, either creates a product or an order in FluentCommerce.

* Connector solution should be lightweight in nature
* Connector solutions should follow test driven development. Unit , Integration (& E2E) tests should be included and successfully passed to be used
* No hardcoding of customer related config. If needed, values in an environment file which should not be maintained in repository
* Connector solution should be supported with detailed documentation
* Connectors should be point to point in nature, currently doesnt support any persistence capabilities apart from in memory persistence
* Connector solution should use open source technologies, although connector itself can be private for specific customer(s)
* Code should not contain console.log statements, use [the included logger](https://github.com/commercetools/merchant-center-application-kit/tree/main/packages-backend/loggers#readme) instead.
## Pre-requisites
- FluentCommerce retail account
- FluentCommerce Lingo account
- commercetools account
- [commercetools API keys](https://docs.commercetools.com/getting-started/create-api-client) (“Admin client”)

## Installing the connector
In order to install the connector in your commercetools project, you'll need to deploy it. Refer to the [commercetools connect deployment documentation](https://docs.commercetools.com/connect/concepts#deployments).

Setup the required environment variables when you [create the deployment](https://docs.commercetools.com/connect/getting-started#create-a-deployment):

- `CTP_CLIENT_ID`
- `CTP_CLIENT_SECRET`
- `CTP_PROJECT_KEY`
- `CTP_SCOPE`
- `CTP_REGION`
- `FLUENT_CLIENT_SECRET`
- `FLUENT_CLIENT_ID`
- `FLUENT_USERNAME`
- `FLUENT_PASSWORD`
- `FLUENT_HOST`
- `FLUENT_CATALOGUE_REF`
- `FLUENT_RETAILER_ID`
- `FLUENT_CATALOG_LOCALE`

Once the connector is deployed, it should trigger the [`postDeploy` script](https://docs.commercetools.com/connect/convert-existing-integration#postdeploy).

The post-deploy script will create a commercetools API subscription to listen to “[Order Created](https://docs.commercetools.com/api/projects/messages#order-created)” and “[Product Published](https://docs.commercetools.com/api/projects/messages#product-published)[messages](https://docs.commercetools.com/api/projects/messages) . Each time an order is created or a product is published, the commercetools API will send a message to the connector.

## Uninstalling the connector

In order to uninstall the connector, you’ll need to [send the appropriate HTTP request and delete it](https://docs.commercetools.com/connect/deployments#delete-deployment).

This will trigger the [`preUndeploy` script](https://docs.commercetools.com/connect/convert-existing-integration#preundeploy) which will delete the messages subscriptions described on the “Installing the connector” section.

## How it works


### Processing Product Changes and first sync

When the connector is getting deployed, it will get all your commercetools products and create them in FluentCommerce. This connector does not sync product category data.

When a product is published on commercetools, an event of type 'ProductPublished' is received. The application checks if this product has a key. If there isn't one, the product is not processed.

If a product key exists, the application proceeds to create or update a 'standard product' and its 'variants' in FluentCommerce.

If a product key exists, but the variant has an SKU equals to that key, the connector will not process the product. This is because standard products should have a unique Ref in FluentCommerce, and SKU will be used for Ref in variants.

The standard product, which represents the main product, includes details like the product name, description. The variants represent different forms of the product, like different sizes or colors and include similar information as the standard product, along with their SKU (Stock Keeping Unit).

### Processing Order Creation

When an order is created on commercetools, an event of type 'OrderCreated' is received. The application checks if this order has a customer email and customer ID. If these fields don't exist, the order is not processed.

If they do, the application proceeds to create an order in FluentCommerce. If the customer associated with the order doesn't exist in FluentCommerce, a new customer is created along with the order.

If there are payments associated with the order, a financial transaction is created in FluentCommerce.

## FAQ

### Why do we need the `FLUENT_CATALOG_LOCALE` env variable?

- By default, commercetools has built-in i18n support. In order to consume the catalog data, we must specify the desired [`LocalizedString`](https://docs.commercetools.com/api/types#localizedstring).



## Useful links
- https://lingo.fluentcommerce.com/asset-library/reference-modules/order/
- https://lingo.fluentcommerce.com/overview/getting-started/glossary/
- https://lingo.fluentcommerce.com/apis/graphql/
- https://lingo.fluentcommerce.com/apis/rest/v4.1/event-api/
8 changes: 4 additions & 4 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ The Fluent Connector, developed by [Orium](https://www.orium.com), is designed t


## Security
In accordance with posted requirements, the Fluent connector has undergone the [commercetools certification process](https://docs.commercetools.com/connect/certification) and has been approved prior to public lease. Specific security requirements are part of this process, including a defined timeline for resolution of reported critical and high vulnerabilities.
In accordance with posted requirements, the Fluent connector has undergone the [commercetools certification process](https://docs.commercetools.com/connect/certification) and has been approved prior to public lease. Specific security requirements are part of this process, including a defined timeline for resolution of reported critical and high vulnerabilities.

While the process certifies the connector, it does not substitute the need to conduct further, ongoing and personalized testing of your holistic product where you utilize this connector. The nature of vulnerabilities in software is that the security landscape changes all the time and continuous scanning is necessary.
While the process certifies the connector, it does not substitute the need to conduct further, ongoing and personalized testing of your holistic product where you utilize this connector. The nature of vulnerabilities in software is that the security landscape changes all the time and continuous scanning is necessary.

## No Liability and Indemnification
The use of the Fluent connector comes with no warranty. It is completely the responsibility of those who download and use the code to take the necessary precautions to utilize the connector in a way that is safe and secure. The code is provided "as is”, without any representations or warranties of any kind, either expressed or implied.

You agree to release, indemnify, and hold Orium and its affiliates and subsidiaries, and their officers, directors, employees and agents, harmless from and against any third party claims, liabilities, damages, losses, and expenses, Your continued use of the product constitutes your acceptance of these terms.
You agree to release, indemnify, and hold Orium and its affiliates and subsidiaries, and their officers, directors, employees and agents, harmless from and against any third party claims, liabilities, damages, losses, and expenses, your continued use of the product constitutes your acceptance of these terms.

## Responsible Disclosure
Researchers and users of any type are welcome to identify potential security issues and submit them as pull requests against the Github repository for consideration. Depending on the veracity of the finding, Orium, in their sole discretion, may choose to compensate the reporter with a reward commensurate to the severity of the finding. Submission of a finding does not guarantee a reward. You may also email us at [email protected].
Researchers and users of any type are welcome to identify potential security issues and submit them as pull requests against the Github repository for consideration. Depending on the veracity of the finding, Orium, in their sole discretion, may choose to compensate the reporter with a reward commensurate to the severity of the finding. Submission of a finding does not guarantee a reward. You may also email us at [email protected].

## Subject to Change
This security policy is subject to change at any time. Notification of an update to this security policy will be communicated via a commit to the Fluent connector repository. Your continued use of the product constitutes acceptance of any changes.
3 changes: 3 additions & 0 deletions event/__mocks__/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// ./__mocks__/axios.js
import mockAxios from 'jest-mock-axios';
export default mockAxios;
1 change: 1 addition & 0 deletions event/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"body-parser": "^1.20.2",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"jest-mock-axios": "^4.7.3",
"listr2": "^6.6.1",
"validator": "^13.11.0"
}
Expand Down
121 changes: 121 additions & 0 deletions event/src/connector/actions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'
import {
MY_SUBSCRIPTION_KEY,
createMySubscription,
deleteMySubscription,
} from './actions'

describe('createMySubscription', () => {
let mockDeletefn: typeof jest.fn
let mockPostfn: typeof jest.fn
let mockApiRoot: ByProjectKeyRequestBuilder

const mockGetResponse = {
body: {
results: [{ version: 1 }],
},
}

beforeEach(() => {
mockDeletefn = jest.fn().mockReturnThis()
mockPostfn = jest.fn().mockReturnThis()
mockApiRoot = {
subscriptions: jest.fn().mockReturnThis(),
get: jest.fn().mockReturnThis(),
withKey: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue(mockGetResponse),
delete: mockDeletefn,
post: mockPostfn,
} as unknown as ByProjectKeyRequestBuilder
})

it('should delete an existing subscription if one exists', async () => {
await createMySubscription(mockApiRoot, 'topicName', 'projectId')

expect(mockDeletefn).toHaveBeenCalledWith({
queryArgs: { version: 1 },
})
expect(mockPostfn).toHaveBeenCalledWith({
body: {
key: MY_SUBSCRIPTION_KEY,
destination: {
projectId: 'projectId',
topic: 'topicName',
type: 'GoogleCloudPubSub',
},
format: {
type: 'CloudEvents',
cloudEventsVersion: '1.0',
},
messages: [
{
resourceTypeId: 'product',
types: ['ProductPublished'],
},
{
resourceTypeId: 'order',
types: ['OrderCreated'],
},
],
},
})
})
it('should POST with expected body', async () => {
await createMySubscription(mockApiRoot, 'topicName', 'projectId')

expect(mockPostfn).toHaveBeenCalledWith({
body: {
key: MY_SUBSCRIPTION_KEY,
destination: {
projectId: 'projectId',
topic: 'topicName',
type: 'GoogleCloudPubSub',
},
format: {
type: 'CloudEvents',
cloudEventsVersion: '1.0',
},
messages: [
{
resourceTypeId: 'product',
types: ['ProductPublished'],
},
{
resourceTypeId: 'order',
types: ['OrderCreated'],
},
],
},
})
})
})

describe('deleteMySubscription', () => {
let mockDeletefn: typeof jest.fn
let mockApiRoot: ByProjectKeyRequestBuilder

const mockGetResponse = {
body: {
results: [{ version: 1 }],
},
}

beforeEach(() => {
mockDeletefn = jest.fn().mockReturnThis()
mockApiRoot = {
subscriptions: jest.fn().mockReturnThis(),
get: jest.fn().mockReturnThis(),
withKey: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue(mockGetResponse),
delete: mockDeletefn,
} as unknown as ByProjectKeyRequestBuilder
})

it('should delete a subscription', async () => {
await deleteMySubscription(mockApiRoot)

expect(mockDeletefn).toHaveBeenCalledWith({
queryArgs: { version: 1 },
})
})
})
20 changes: 10 additions & 10 deletions event/src/connector/actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder';
import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'

const MY_SUBSCRIPTION_KEY = 'ct-connect-fluent-subscription';
export const MY_SUBSCRIPTION_KEY = 'ct-connect-fluent-subscription'

export async function createMySubscription(
apiRoot: ByProjectKeyRequestBuilder,
Expand All @@ -16,10 +16,10 @@ export async function createMySubscription(
where: `key = "${MY_SUBSCRIPTION_KEY}"`,
},
})
.execute();
.execute()

if (subscriptions.length > 0) {
const subscription = subscriptions[0];
const subscription = subscriptions[0]

await apiRoot
.subscriptions()
Expand All @@ -29,7 +29,7 @@ export async function createMySubscription(
version: subscription.version,
},
})
.execute();
.execute()
}

await apiRoot
Expand All @@ -54,11 +54,11 @@ export async function createMySubscription(
{
resourceTypeId: 'order',
types: ['OrderCreated'],
}
},
],
},
})
.execute();
.execute()
}

export async function deleteMySubscription(
Expand All @@ -73,10 +73,10 @@ export async function deleteMySubscription(
where: `key = "${MY_SUBSCRIPTION_KEY}"`,
},
})
.execute();
.execute()

if (subscriptions.length > 0) {
const subscription = subscriptions[0];
const subscription = subscriptions[0]

await apiRoot
.subscriptions()
Expand All @@ -86,6 +86,6 @@ export async function deleteMySubscription(
version: subscription.version,
},
})
.execute();
.execute()
}
}
Loading

0 comments on commit c14936b

Please sign in to comment.