Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document Record Service #274

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
642 changes: 642 additions & 0 deletions src/enums/document-types.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './restoration-types'
export * from './role-types'
export * from './staff-payment-options'
export * from './relationship-types'
export * from './document-types'

// export the enums from corp type module
// DEPRECATED: stop this -- import from corp-type-module instead
Expand Down
40 changes: 21 additions & 19 deletions src/enums/sbc-common-components-constants.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
export enum SessionStorageKeys {
KeyCloakToken = 'KEYCLOAK_TOKEN',
KeyCloakRefreshToken = 'KEYCLOAK_REFRESH_TOKEN',
KeyCloakIdToken = 'KEYCLOAK_ID_TOKEN',
AffidavitNeeded = 'AFFIDAVIT_NEEDED',
ApiConfigKey = 'AUTH_API_CONFIG',
PreventStorageSync = 'PREVENT_STORAGE_SYNC',
LaunchDarklyFlags = 'LD_FLAGS',
CurrentAccount = 'CURRENT_ACCOUNT',
AuthApiUrl = 'AUTH_API_URL',
AuthWebUrl = 'AUTH_WEB_URL',
StatusApiUrl = 'STATUS_API_URL',
WhatsNew = 'WHATS_NEW',
SessionSynced = 'SESSION_SYNCED',
RegistryHomeUrl = 'REGISTRY_HOME_URL',
NameRequestUrl = 'NAME_REQUEST_URL',
PprWebUrl = 'PPR_WEB_URL',
SiteminderLogoutUrl = 'SITEMINDER_LOGOUT_URL',
BusinessIdentifierKey = 'BUSINESS_ID',
CurrentAccount = 'CURRENT_ACCOUNT',
DocApiKey = 'DOC_API_KEY',
DocApiUrl = 'DOC_API_URL',
ExtraProvincialUser = 'EXTRAPROVINCIAL_USER',
FasWebUrl = 'FAS_WEB_URL',
GovnUser = 'AUTH_GOVN_USER',
InvitationToken = 'INV_TOKEN',
PaginationOptions = 'PAGINATION_OPTIONS',
PaginationNumberOfItems = 'PAGINATION_NUMBER_OF_ITEMS',
KeyCloakIdToken = 'KEYCLOAK_ID_TOKEN',
KeyCloakRefreshToken = 'KEYCLOAK_REFRESH_TOKEN',
KeyCloakToken = 'KEYCLOAK_TOKEN',
LaunchDarklyFlags = 'LD_FLAGS',
NameRequestUrl = 'NAME_REQUEST_URL',
OrgSearchFilter = 'ORG_SEARCH_FILTER',
PaginationNumberOfItems = 'PAGINATION_NUMBER_OF_ITEMS',
PaginationOptions = 'PAGINATION_OPTIONS',
PayApiUrl = 'PAY_API_URL',
PendingAccountsSearchFilter = 'PENDING_ACCOUNTS_SEARCH_FILTER',
PprWebUrl = 'PPR_WEB_URL',
PreventStorageSync = 'PREVENT_STORAGE_SYNC',
RegistryHomeUrl = 'REGISTRY_HOME_URL',
RejectedAccountsSearchFilter = 'REJECTED_ACCOUNTS_SEARCH_FILTER',
FasWebUrl = 'FAS_WEB_URL',
AffidavitNeeded = 'AFFIDAVIT_NEEDED',
GOVN_USER='AUTH_GOVN_USER',
PayApiUrl = 'PAY_API_URL'
SessionSynced = 'SESSION_SYNCED',
SiteminderLogoutUrl = 'SITEMINDER_LOGOUT_URL',
StatusApiUrl = 'STATUS_API_URL',
WhatsNew = 'WHATS_NEW'
}

export enum Account {
Expand Down
145 changes: 145 additions & 0 deletions src/services/document-services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { AxiosInstance, AxiosResponse } from 'axios'
import { SessionStorageKeys } from '@bcrs-shared-components/enums/sbc-common-components-constants'
import ConfigHelper from './utils/config-helper'

export default class DocumentService {
/**
* Retrieves the configuration for making an API request to the Document Record service.
* @returns an object containing the URL and headers for the DRS API request.
*/
static requestConfig () {
return {
url: ConfigHelper.getFromSession('DOC_API_URL'),
headers: {
'x-apikey': ConfigHelper.getFromSession('DOC_API_KEY'),
'Account-Id': JSON.parse(ConfigHelper.getFromSession(SessionStorageKeys.CurrentAccount) || '{}')?.id || 0
}
}
}

/**
* Uploads the specified file to Document Record Service.
* @param file the file to upload
* @param documentClass the document class defined for the document service. e.g. 'CORP'
* @param documentType the type of document. e.g. 'CNTA'
* @param businessId the business identifier(tempId or businessId)
* @param consumerDocumentId the identifier of one or more documents associated with the filing.
* @returns a promise to return the axios response or the error response
*/
static async uploadDocumentToDRS (
axios: AxiosInstance,
document: File,
documentClass: string,
documentType: string,
businessId: string,
consumerDocumentId: string = undefined,
consumerFilingDate: string = new Date().toISOString()
): Promise<AxiosResponse> {
// Set request params.
let url = `${DocumentService.requestConfig().url}/documents/${documentClass}/${documentType}`
url += `?consumerFilingDate=${consumerFilingDate}&consumerFilename=${document.name}`
url += `&consumerIdentifier=${businessId}`
if (consumerDocumentId) {
url += `&consumerDocumentId=${consumerDocumentId}`
}

return axios
.post(url, document, {
headers: {
...DocumentService.requestConfig().headers,
'Content-Type': 'application/pdf'
}
})
.then((response) => {
return response
})
.catch((error) => {
return error.response
})
}

/**
* Replace the existing document record specified by the document service ID.
* @param documentServiceId the unique identifier of document on Document Record Service
* @param file the file to replace
* @param documentName the file name to replace
* @returns a promise to return the axios response or the error response
*/
static async updateDocumentOnDRS (
axios: AxiosInstance,
document: File,
documentServiceId: string,
documentName: string
) {
let url = `${DocumentService.requestConfig().url}/documents/${documentServiceId}`
url += `?consumerFilename=${documentName}`

return axios
.put(url, document, {
headers: {
...DocumentService.requestConfig().headers,
'Content-Type': 'application/pdf'
}
})
.then((response) => {
return response
})
.catch((error) => {
return error.response
})
}

/**
* Deletes a document from Document Record Service.
* @param documentServiceId the unique identifier of document on Document Record Service
* @returns a promise to return the axios response or the error response
*/
static async deleteDocumentFromDRS (axios: AxiosInstance, documentServiceId: string): Promise<AxiosResponse> {
// safety checks
if (!documentServiceId) {
throw new Error('Invalid parameters')
}
const url = `${DocumentService.requestConfig().url}/documents/${documentServiceId}`

return axios.patch(url, { removed: true }, { headers: DocumentService.requestConfig().headers })
}

/**
* Download the specified file from Document Record Service.
* @param documentKey the unique id on Document Record Service
* @param documentClass the document class defined for the document service. e.g. 'CORP'
* @param documentName the document name to download
* @returns void
*/
static async downloadDocumentFromDRS (
axios: AxiosInstance,
documentKey: string,
documentName: string,
documentClass: string
): Promise<void> {
// safety checks
if (!documentKey || !documentName) {
throw new Error('Invalid parameters')
}

const url = `${DocumentService.requestConfig().url}/searches/${documentClass}?documentServiceId=${documentKey}`

axios.get(url, { headers: DocumentService.requestConfig().headers }).then((response) => {
if (!response) {
throw new Error('Null response')
}

const link = document.createElement('a')
link.href = response.data[0].documentURL
link.download = documentName
link.target = '_blank' // This opens the link in a new browser tab

// Append to the document and trigger the download
document.body.appendChild(link)
link.click()

// Remove the link after the download is triggered
document.body.removeChild(link)
})
}
}
6 changes: 3 additions & 3 deletions src/services/fee-services.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Axios from 'axios'
import { Fee, FilingData, PayData } from '@/interfaces'
import ConfigHelper from '@/utils/config-helper'
import { SessionStorageKeys } from '@/enums/sbc-common-components-constants'
import { Fee, FilingData, PayData } from '@bcrs-shared-components/interfaces'
import { SessionStorageKeys } from '@bcrs-shared-components/enums/sbc-common-components-constants'
import ConfigHelper from './utils/config-helper'

// sample Microcks URLs =
// https://mock-lear-tools.pathfinder.gov.bc.ca/rest/SBC+Pay+API+Reference/1.0.1/api/v1/fees/CP/OTANN
Expand Down
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default as FeeServices } from './fee-services'
export { default as DocumentServices } from './document-services'
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ConfigHelper from './config-helper'
import { SessionStorageKeys, ACCOUNT_ID } from '../enums/sbc-common-components-constants'
import { SessionStorageKeys, ACCOUNT_ID } from '@bcrs-shared-components/enums/sbc-common-components-constants'

/**
* Place to put all the custom utility methods
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SessionStorageKeys } from '@/enums/sbc-common-components-constants'
import { trimTrailingSlashURL } from '@/utils/common-util'
import { SessionStorageKeys } from '@bcrs-shared-components/enums/sbc-common-components-constants'
import { trimTrailingSlashURL } from './common-util'

export default class ConfigHelper {
static keycloakConfigUrl = ''
Expand Down
1 change: 0 additions & 1 deletion src/utils/index.ts → src/services/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { sleep } from './sleep'
export * from './common-util'
export * from './config-helper'
8 changes: 0 additions & 8 deletions src/utils/sleep.ts

This file was deleted.

2 changes: 1 addition & 1 deletion tests/unit/DetailComment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Vuetify from 'vuetify'
import { shallowMount } from '@vue/test-utils'
import { DetailComment } from '@/components/detail-comment'
import { sleep } from '@/utils/sleep'
import { sleep } from '../utils'
import flushPromises from 'flush-promises'

const vuetify = new Vuetify({})
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/StaffComments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import axios from 'axios'
import sinon from 'sinon'
import { mount, Wrapper } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import { sleep } from '@/utils/sleep'
import { sleep } from '../utils'
import { StaffComments } from '@/components/staff-comments'

// suppress the "[Vuetify] Unable to locate target [data-app]" warning
Expand Down
119 changes: 119 additions & 0 deletions tests/unit/services/DocumentService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import MockAdapter from 'axios-mock-adapter'
import documentService from '@/services/document-services'
import { DOCUMENT_TYPES } from '@/enums'
import { SessionStorageKeys } from '@/enums/sbc-common-components-constants'
import { getAxiosInstance } from 'tests/utils'

describe('documentService', () => {
const axiosInstance = getAxiosInstance()
const mock = new MockAdapter(axiosInstance)
const docApiUrl = 'https://api.example.com/doc/api/v1'
const docApiKey = 'test-doc-api-key'
const accountId = '1010'
const documentClass = DOCUMENT_TYPES.corpContInAuthorization.class
const documentType = DOCUMENT_TYPES.corpContInAuthorization.type
const consumerIdentifier = 'T0aAaaAAAA'
const consumerFilingDate = '2025-03-06T22:06:53.870Z'
const documentName = 'cont.in.authorization.pdf'
const documentServiceId = 'DS0000000001'
const documentURL = 'https://api.googlestorage.com/some/drs/api?some-file-name-with-id.pdf'

sessionStorage.setItem(SessionStorageKeys.DocApiUrl, docApiUrl)
sessionStorage.setItem(SessionStorageKeys.DocApiKey, docApiKey)
sessionStorage.setItem(SessionStorageKeys.CurrentAccount, JSON.stringify({ id: accountId }))

beforeEach(() => {
mock.reset()
})

it('should upload a file successfully', async () => {
const file = new File(['test-content'], documentName, { type: 'application/pdf' })

let url = `${docApiUrl}/documents/${documentClass}/${documentType}`
url += `?consumerFilingDate=${consumerFilingDate}&consumerFilename=${file.name}`
url += `&consumerIdentifier=${consumerIdentifier}`

mock
.onPost(url, file, {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/pdf',
'x-apikey': docApiKey,
'Account-Id': accountId
})
.reply(201, {
consumerIdentifier: consumerIdentifier,
documentType: documentType
})

const response = await documentService.uploadDocumentToDRS(
axiosInstance,
file,
documentClass,
documentType,
consumerIdentifier,
null,
consumerFilingDate
)

expect(response.status).toEqual(201)
expect(response.data).toHaveProperty('consumerIdentifier', consumerIdentifier)
expect(response.data).toHaveProperty('documentType', documentType)
expect(mock.history.post.length).toBe(1)
expect(mock.history.post[0].headers).toMatchObject({
'x-apikey': docApiKey,
'Account-Id': accountId
})
})

it('should update DRS record successfully', async () => {
const file = new File(['test-content'], documentName, { type: 'application/pdf' })

let url = `${docApiUrl}/documents/${documentServiceId}`
url += `?consumerFilename=${documentName}`

mock
.onPut(url, file, {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/pdf',
'x-apikey': docApiKey,
'Account-Id': accountId
})
.reply(200, {
consumerIdentifier: consumerIdentifier,
documentType: documentType
})

const response = await documentService.updateDocumentOnDRS(axiosInstance, file, documentServiceId, documentName)

expect(response.status).toEqual(200)
expect(response.data).toHaveProperty('consumerIdentifier', consumerIdentifier)
expect(mock.history.put.length).toBe(1)
expect(mock.history.put[0].headers).toMatchObject({
'x-apikey': docApiKey,
'Account-Id': accountId
})
})

it('should delete a document successfully', async () => {
const url = `${docApiUrl}/documents/${documentServiceId}`

mock.onPatch(url, { removed: true }).reply(200, {})

const response = await documentService.deleteDocumentFromDRS(axiosInstance, documentServiceId)

expect(response.status).toBe(200)
expect(mock.history.patch.length).toBe(1)
expect(mock.history.patch[0].url).toBe(url)
})

it('should download a document successfully', async () => {
const url = `${docApiUrl}/searches/${documentClass}?documentServiceId=${documentServiceId}`

mock.onGet(url).reply(200, [{ documentURL: documentURL }])

await documentService.downloadDocumentFromDRS(axiosInstance, documentServiceId, documentName, documentClass)

expect(mock.history.get.length).toBe(1)
expect(mock.history.get[0].url).toBe(url)
})
})
Loading
Loading