Skip to content

Commit

Permalink
Services and utils unit tests (#83)
Browse files Browse the repository at this point in the history
* feat: tests for api client

* feat: added test for environment service

* feat: added payment methods test

* feat: tests for payments service

* feat: payments details tests

* feat: added test for utils function

* feat: added tests for error response function

* feat: added tests for executing callbacks

* feat: added tests for formating address and parsers

* feat: lint

* feat: changed package dependency to point to local pacakge

* feat: added pre-push script

* feat: added pre-push script

* fix: remove token mock from constants

* fix: exclude tests from codeql

* fix: no need for output files

* fix: codequl yml file

* chore: upload artifacts

* feat: basic codeql setup

* fix: remove matrix language

* chore: exclude scripts from testing

* fix: remove unused variable
  • Loading branch information
amihajlovski authored Dec 14, 2023
1 parent 40619e6 commit b34650d
Show file tree
Hide file tree
Showing 20 changed files with 580 additions and 41 deletions.
30 changes: 2 additions & 28 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,13 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
uses: actions/checkout@v4

# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v2
5 changes: 5 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

cd packages/adyen-salesforce-pwa
npm run test
6 changes: 3 additions & 3 deletions packages/adyen-retail-react-app/build/loadable-stats.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client",
"hash": "84e140465b7193b7b54a",
"hash": "31d19a4495175b725991",
"publicPath": "/mobify/bundle/development/",
"outputPath": "/Users/aleksandarm/Repositories/adyen-salesforce-headless-commerce-pwa/packages/adyen-retail-react-app/build",
"assetsByChunkName": {
Expand Down Expand Up @@ -54,7 +54,7 @@
{
"type": "asset",
"name": "vendor.js",
"size": 8676138,
"size": 8692636,
"emitted": false,
"comparedForEmit": false,
"cached": true,
Expand All @@ -81,7 +81,7 @@
{
"type": "asset",
"name": "main.js",
"size": 1221022,
"size": 1221139,
"emitted": false,
"comparedForEmit": false,
"cached": true,
Expand Down
12 changes: 6 additions & 6 deletions packages/adyen-retail-react-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/adyen-retail-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"style-loader": "^3.3.3"
},
"dependencies": {
"@adyen/adyen-salesforce-pwa": "1.0.0-beta.3"
"@adyen/adyen-salesforce-pwa": "file:../adyen-salesforce-pwa"
},
"scripts": {
"analyze-build": "cross-env MOBIFY_ANALYZE=true npm run build",
Expand Down
3 changes: 2 additions & 1 deletion packages/adyen-salesforce-pwa/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ module.exports = {
},
collectCoverageFrom: [
'lib/**/*.{js,jsx,mjs,cjs}',
'!lib/scripts/**/*{js,jsx,mjs,cjs}',
'!**/node_modules/**',
'!**/dist/**',
'!**/mocks/**'
'!**/mocks/**',
],
clearMocks: true,
restoreMocks: true,
Expand Down
81 changes: 81 additions & 0 deletions packages/adyen-salesforce-pwa/lib/services/tests/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {ApiClient} from '../api'
import {ADYEN_API_BASEPATH} from '../../../mocks/adyenApi/constants'

describe('ApiClient', () => {
let apiClient

beforeEach(() => {
apiClient = new ApiClient(ADYEN_API_BASEPATH, 'mockToken')
})

afterEach(() => {
jest.restoreAllMocks()
})

it('should construct ApiClient with url and token', () => {
expect(apiClient.url).toBe(ADYEN_API_BASEPATH)
expect(apiClient.token).toBe('mockToken')
})

it('should call fetch with correct parameters for GET request', async () => {
const mockResponse = {data: 'some data'}
const mockJsonPromise = Promise.resolve(mockResponse)
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
ok: true
})

jest.spyOn(global, 'fetch').mockImplementation(() => mockFetchPromise)

const options = {
queryParams: {param: 'value'},
headers: {customHeader: 'customValue'}
}

await apiClient.get(options)

expect(global.fetch).toHaveBeenCalledWith(
`${ADYEN_API_BASEPATH}?param=value`,
expect.objectContaining({
method: 'get',
body: null,
headers: {
'Content-Type': 'application/json',
authorization: `Bearer mockToken`,
customHeader: 'customValue'
}
})
)
})

it('should call fetch with correct parameters for POST request', async () => {
const mockResponse = {success: true}
const mockJsonPromise = Promise.resolve(mockResponse)
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
ok: true
})

jest.spyOn(global, 'fetch').mockImplementation(() => mockFetchPromise)

const options = {
body: JSON.stringify({key: 'value'}),
headers: {customHeader: 'customValue'}
}

await apiClient.post(options)

expect(global.fetch).toHaveBeenCalledWith(
ADYEN_API_BASEPATH,
expect.objectContaining({
method: 'post',
body: JSON.stringify({key: 'value'}),
headers: {
'Content-Type': 'application/json',
authorization: `Bearer mockToken`,
customHeader: 'customValue'
}
})
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {AdyenEnvironmentService} from '../environment'
import {ApiClient} from '../api'

jest.mock('../api', () => {
return {
ApiClient: jest.fn().mockImplementation(() => ({
get: jest.fn()
}))
}
})

describe('AdyenEnvironmentService', () => {
let adyenService
let mockToken = 'mockToken'

beforeEach(() => {
adyenService = new AdyenEnvironmentService(mockToken)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should create an instance of AdyenEnvironmentService with ApiClient', () => {
expect(ApiClient).toHaveBeenCalledWith('/api/adyen/environment', mockToken)
})

it('should fetch environment successfully', async () => {
const mockResponse = {environmentData: 'some data'}
const mockJsonPromise = Promise.resolve(mockResponse)
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
status: 200
})

adyenService.apiClient.get.mockResolvedValueOnce(mockFetchPromise)

const environmentData = await adyenService.fetchEnvironment()

expect(adyenService.apiClient.get).toHaveBeenCalled()
expect(environmentData).toEqual(mockResponse)
})

it('should throw an error when fetchEnvironment gets a status >= 300', async () => {
const mockFetchPromise = Promise.resolve({
status: 400,
statusText: 'Bad Request'
})

adyenService.apiClient.get.mockResolvedValueOnce(mockFetchPromise)

await expect(adyenService.fetchEnvironment()).rejects.toThrow('[object Object]')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {AdyenPaymentMethodsService} from '../payment-methods'
import {ApiClient} from '../api'
import {CUSTOMER_ID_MOCK, LOCALE_MOCK} from '../../../mocks/adyenApi/constants'

jest.mock('../api', () => {
return {
ApiClient: jest.fn().mockImplementation(() => ({
get: jest.fn()
}))
}
})

describe('AdyenPaymentMethodsService', () => {
let paymentMethodsService
let mockToken = 'mockToken'
let mockCustomerId = CUSTOMER_ID_MOCK
let mockLocale = {id: LOCALE_MOCK}

beforeEach(() => {
paymentMethodsService = new AdyenPaymentMethodsService(mockToken)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should create an instance of AdyenPaymentMethodsService with ApiClient', () => {
expect(ApiClient).toHaveBeenCalledWith('/api/adyen/paymentMethods', mockToken)
})

it('should fetch payment methods successfully', async () => {
const mockResponse = {paymentMethods: ['visa', 'paypal']}
const mockJsonPromise = Promise.resolve(mockResponse)
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
status: 200
})

paymentMethodsService.apiClient.get.mockResolvedValueOnce(mockFetchPromise)

const paymentMethods = await paymentMethodsService.fetchPaymentMethods(
mockCustomerId,
mockLocale
)

expect(paymentMethodsService.apiClient.get).toHaveBeenCalledWith({
queryParams: {locale: mockLocale.id},
headers: {customerid: mockCustomerId}
})
expect(paymentMethods).toEqual(mockResponse)
})

it('should throw an error when fetchPaymentMethods gets a status >= 300', async () => {
const mockFetchPromise = Promise.resolve({
status: 400,
statusText: 'Bad Request'
})

paymentMethodsService.apiClient.get.mockResolvedValueOnce(mockFetchPromise)

await expect(
paymentMethodsService.fetchPaymentMethods(mockCustomerId, mockLocale)
).rejects.toThrow('[object Object]')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {AdyenPaymentsDetailsService} from '../payments-details'
import {ApiClient} from '../api'

jest.mock('../api', () => {
return {
ApiClient: jest.fn().mockImplementation(() => ({
post: jest.fn()
}))
}
})

describe('AdyenPaymentsDetailsService', () => {
let paymentsDetailsService
let mockToken = 'mockTokenHere'
let mockData = {someData: 'mockData'}
let mockCustomerId = 'mockCustomerId'

beforeEach(() => {
paymentsDetailsService = new AdyenPaymentsDetailsService(mockToken)
})

afterEach(() => {
jest.clearAllMocks()
})

it('should create an instance of AdyenPaymentsDetailsService with ApiClient', () => {
expect(paymentsDetailsService).toBeInstanceOf(AdyenPaymentsDetailsService)
expect(ApiClient).toHaveBeenCalledWith('/api/adyen/payments/details', mockToken)
})

it('should submit payment details successfully', async () => {
const mockResponse = {paymentDetailsResult: 'success'}
const mockJsonPromise = Promise.resolve(mockResponse)
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
status: 200
})

paymentsDetailsService.apiClient.post.mockResolvedValueOnce(mockFetchPromise)

const paymentDetailsResult = await paymentsDetailsService.submitPaymentsDetails(
mockData,
mockCustomerId
)

expect(paymentsDetailsService.apiClient.post).toHaveBeenCalledWith({
body: JSON.stringify({data: mockData}),
headers: {
customerid: mockCustomerId
}
})
expect(paymentDetailsResult).toEqual(mockResponse)
})

it('should throw an error when submitPaymentsDetails gets a status >= 300', async () => {
const mockFetchPromise = Promise.resolve({
status: 400,
statusText: 'Bad Request'
})

paymentsDetailsService.apiClient.post.mockResolvedValueOnce(mockFetchPromise)

await expect(
paymentsDetailsService.submitPaymentsDetails(mockData, mockCustomerId)
).rejects.toThrow('[object Object]')
})
})
Loading

0 comments on commit b34650d

Please sign in to comment.