diff --git a/integrationTests/Files/getFileById.spec.ts b/integrationTests/Files/getFileById.spec.ts new file mode 100644 index 0000000..fec3840 --- /dev/null +++ b/integrationTests/Files/getFileById.spec.ts @@ -0,0 +1,185 @@ +import { OnspringClient } from '../../src/index'; +import { expect } from 'chai'; +import { baseURL, apiKey } from '../mochaRootHooks'; + +describe('getFileById', function () { + this.timeout(30000); + this.retries(3); + + it('should return a file in an attachment field', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_RECORD === undefined) { + expect.fail('TEST_RECORD is not defined'); + } + + if (process.env.TEST_ATTACHMENT_FIELD === undefined) { + expect.fail('TEST_ATTACHMENT_FIELD is not defined'); + } + + if (process.env.TEST_ATTACHMENT === undefined) { + expect.fail('TEST_ATTACHMENT is not defined'); + } + + const recordId = parseInt(process.env.TEST_RECORD); + const fieldId = parseInt(process.env.TEST_ATTACHMENT_FIELD); + const fileId = parseInt(process.env.TEST_ATTACHMENT); + + const response = await client.getFileById(recordId, fieldId, fileId); + + expect(response.statusCode).to.equal(200); + expect(response.isSuccessful).to.be.true; + expect(response.message).to.equal(''); + expect(response.data).to.not.be.null; + + if (response.data != null) { + expect(response.data.contentLength).to.not.be.null; + expect(response.data.contentType).to.not.be.null; + expect(response.data.fileName).to.not.be.null; + expect(response.data.stream).to.not.be.null; + } + }); + + it('should return a file in an image field', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_RECORD === undefined) { + expect.fail('TEST_RECORD is not defined'); + } + + if (process.env.TEST_IMAGE_FIELD === undefined) { + expect.fail('TEST_IMAGE_FIELD is not defined'); + } + + if (process.env.TEST_IMAGE === undefined) { + expect.fail('TEST_IMAGE is not defined'); + } + + const recordId = parseInt(process.env.TEST_RECORD); + const fieldId = parseInt(process.env.TEST_IMAGE_FIELD); + const fileId = parseInt(process.env.TEST_IMAGE); + const response = await client.getFileById(recordId, fieldId, fileId); + + expect(response.statusCode).to.equal(200); + expect(response.isSuccessful).to.be.true; + expect(response.message).to.equal(''); + expect(response.data).to.not.be.null; + + if (response.data != null) { + expect(response.data.contentLength).to.not.be.null; + expect(response.data.contentType).to.not.be.null; + expect(response.data.fileName).to.not.be.null; + expect(response.data.stream).to.not.be.null; + } + }); + + it('should return a 400 response when fieldId is not for a file field', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_RECORD === undefined) { + expect.fail('TEST_RECORD is not defined'); + } + + if (process.env.TEST_TEXT_FIELD === undefined) { + expect.fail('TEST_TEXT_FIELD is not defined'); + } + + if (process.env.TEST_ATTACHMENT === undefined) { + expect.fail('TEST_ATTACHMENT is not defined'); + } + + const recordId = parseInt(process.env.TEST_RECORD); + const fieldId = parseInt(process.env.TEST_TEXT_FIELD); + const fileId = parseInt(process.env.TEST_ATTACHMENT); + const response = await client.getFileById(recordId, fieldId, fileId); + + expect(response.statusCode).to.equal(400); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.not.be.null.and.not.be.undefined; + expect(response.data).to.be.null; + }); + + it('should return a 401 response when the api key is invalid', async function () { + const client = new OnspringClient(baseURL, 'invalid'); + + if (process.env.TEST_RECORD === undefined) { + expect.fail('TEST_RECORD is not defined'); + } + + if (process.env.TEST_ATTACHMENT_FIELD === undefined) { + expect.fail('TEST_ATTACHMENT_FIELD is not defined'); + } + + if (process.env.TEST_ATTACHMENT === undefined) { + expect.fail('TEST_ATTACHMENT is not defined'); + } + + const recordId = parseInt(process.env.TEST_RECORD); + const fieldId = parseInt(process.env.TEST_ATTACHMENT_FIELD); + const fileId = parseInt(process.env.TEST_ATTACHMENT); + const response = await client.getFileById(recordId, fieldId, fileId); + + expect(response.statusCode).to.equal(401); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.be.undefined; + expect(response.data).to.be.null; + }); + + it('should return a 403 response when the api key does not have access to the file field', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_ATTACHMENT_FIELD_NO_ACCESS_FIELD === undefined) { + expect.fail('TEST_ATTACHMENT_FIELD_NO_ACCESS_FIELD is not defined'); + } + + const fieldId = parseInt(process.env.TEST_ATTACHMENT_FIELD_NO_ACCESS_FIELD); + const response = await client.getFileById(1, fieldId, 1); + + expect(response.statusCode).to.equal(403); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.not.be.null.and.to.not.be.undefined; + expect(response.data).to.be.null; + }); + + it('should return a 403 response when the api key does not have access to the app where the file is held', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_ATTACHMENT_FIELD_NO_ACCESS_APP === undefined) { + expect.fail('EST_ATTACHMENT_FIELD_NO_ACCESS_APP is not defined'); + } + + const fieldId = parseInt(process.env.TEST_ATTACHMENT_FIELD_NO_ACCESS_APP); + const response = await client.getFileById(1, fieldId, 1); + + expect(response.statusCode).to.equal(403); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.not.be.null.and.to.not.be.undefined; + expect(response.data).to.be.null; + }); + + it('should return a 404 response when the file field cannot be found', async function () { + const client = new OnspringClient(baseURL, apiKey); + const response = await client.getFileById(1, 0, 1); + + expect(response.statusCode).to.equal(404); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.not.be.null.and.to.not.be.undefined; + expect(response.data).to.be.null; + }); + + it('should return a 404 response when the file record cannot be found', async function () { + const client = new OnspringClient(baseURL, apiKey); + + if (process.env.TEST_ATTACHMENT_FIELD === undefined) { + expect.fail('TEST_ATTACHMENT_FIELD is not defined'); + } + + const fieldId = parseInt(process.env.TEST_ATTACHMENT_FIELD); + const response = await client.getFileById(0, fieldId, 1); + + expect(response.statusCode).to.equal(404); + expect(response.isSuccessful).to.be.false; + expect(response.message).to.not.be.null.and.to.not.be.undefined; + expect(response.data).to.be.null; + }); +}); diff --git a/package.json b/package.json index 336fa2d..bca3f00 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "license": "MIT", "engines": { - "node": ">=18.14.0" + "node": ">=12.0.0" }, "main": "dist/cjs/index.js", "types": "dist/index.d.ts", diff --git a/src/models/ApiResponseFactory.ts b/src/models/ApiResponseFactory.ts index 53ad17a..f4cc601 100644 --- a/src/models/ApiResponseFactory.ts +++ b/src/models/ApiResponseFactory.ts @@ -1,6 +1,7 @@ import { type AxiosResponse } from 'axios'; import { HttpStatusCode } from '../enums/HttpStatusCode'; import { ApiResponse } from './ApiResponse'; +import { type Readable } from 'stream'; /** * @class ApiResponseFactory - Factory class for creating ApiResponse objects @@ -9,10 +10,12 @@ export class ApiResponseFactory { /** * @method getApiResponse - Creates an ApiResponse object from an AxiosResponse object * @param {AxiosResponse} response - The AxiosResponse object that will be used to create the ApiResponse object - * @returns {ApiResponse} - An ApiResponse object of type T + * @returns {Promise>} - A promise that resolves to an ApiResponse object */ - public static getApiResponse(response: AxiosResponse): ApiResponse { - const message = this.TryToGetMessage(response); + public static async getApiResponse( + response: AxiosResponse + ): Promise> { + const message = await this.TryToGetMessage(response); if (this.isSuccessStatusCode(response.status) === true) { return new ApiResponse(response.status, message, response.data); @@ -24,24 +27,53 @@ export class ApiResponseFactory { /** * @method TryToGetMessage - Attempts to get the message from the response data * @param {AxiosResponse} response - The AxiosResponse object that will be used to get the message - * @returns {string} - The message from the response data + * @returns {Promise} - A promise that resolves to the message */ - private static TryToGetMessage(response: AxiosResponse): string { + private static async TryToGetMessage( + response: AxiosResponse + ): Promise { if ( response.status === HttpStatusCode.Unauthorized || response.status === HttpStatusCode.Forbidden || response.status === HttpStatusCode.NotFound ) { + if (response.config.responseType === 'stream') { + const dataString = await this.getStreamDataAsString(response.data); + const dataObject = + dataString.length > 0 ? JSON.parse(dataString) : '{}'; + return dataObject.message; + } + return response.data?.message; } if (this.isSuccessStatusCode(response.status) === false) { + if (response.config.responseType === 'stream') { + const data = response.data as Readable; + return await this.getStreamDataAsString(data); + } + return JSON.stringify(response.data); } return ''; } + /** + * + */ + private static async getStreamDataAsString( + stream: Readable + ): Promise { + const chunks = [] as Buffer[]; + + for await (const chunk of stream) { + chunks.push(chunk); + } + + return Buffer.concat(chunks).toString('utf-8'); + } + /** * @method isSuccessStatusCode - Determines if the specified status code is a success status code * @param {number} statusCode - The status code that will be used to determine if it is a success status code diff --git a/src/models/OnspringClient.ts b/src/models/OnspringClient.ts index b10308b..2cdf228 100644 --- a/src/models/OnspringClient.ts +++ b/src/models/OnspringClient.ts @@ -252,7 +252,7 @@ export class OnspringClient { responseType: 'stream', }); - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); if (apiResponse.isSuccessful === false) { return apiResponse; @@ -511,7 +511,7 @@ export class OnspringClient { config: AxiosRequestConfig = {} ): Promise> { const response = await this._client.get(endpoint, config); - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); return apiResponse; } @@ -528,7 +528,7 @@ export class OnspringClient { config: AxiosRequestConfig = {} ): Promise> { const response = await this._client.post(endpoint, data, config); - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); return apiResponse; } @@ -545,7 +545,7 @@ export class OnspringClient { config: AxiosRequestConfig = {} ): Promise> { const response = await this._client.put(endpoint, data, config); - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); return apiResponse; } @@ -560,7 +560,7 @@ export class OnspringClient { config: AxiosRequestConfig = {} ): Promise> { const response = await this._client.delete(endpoint, config); - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); return apiResponse; } } diff --git a/tests/ApiResponseFactory.spec.ts b/tests/ApiResponseFactory.spec.ts index b8cc2e0..7c13612 100644 --- a/tests/ApiResponseFactory.spec.ts +++ b/tests/ApiResponseFactory.spec.ts @@ -16,7 +16,7 @@ describe('ApiResponseFactory', function () { expect(ApiResponseFactory.getApiResponse).to.have.lengthOf(1); }); - it('should return an ApiResponse object when request is successful', function () { + it('should return an ApiResponse object when request is successful', async function () { const response: AxiosResponse = { data: null, status: 200, @@ -25,7 +25,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -36,7 +36,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object without a message value when request is forbidden and the response does not contain a message property', function () { + it('should return an ApiResponse object without a message value when request is forbidden and the response does not contain a message property', async function () { const response: AxiosResponse = { data: null, status: 403, @@ -45,7 +45,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -56,7 +56,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object when request is forbidden and the response contains a message property', function () { + it('should return an ApiResponse object when request is forbidden and the response contains a message property', async function () { const response: AxiosResponse = { data: { message: 'Does not have permission to access this resource.', @@ -67,7 +67,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -80,7 +80,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object without a message value when request is not found and the response does not contain a message property', function () { + it('should return an ApiResponse object without a message value when request is not found and the response does not contain a message property', async function () { const response: AxiosResponse = { data: null, status: 404, @@ -89,7 +89,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -100,7 +100,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object with a message value when request is not found and the response contains a message property', function () { + it('should return an ApiResponse object with a message value when request is not found and the response contains a message property', async function () { const response: AxiosResponse = { data: { message: 'Resource not found.', @@ -111,7 +111,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -122,7 +122,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object without a message value when request is unauthorized and the response does not contain a message property', function () { + it('should return an ApiResponse object without a message value when request is unauthorized and the response does not contain a message property', async function () { const response: AxiosResponse = { data: null, status: 401, @@ -131,7 +131,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -142,7 +142,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object with a message when request is unauthorized and the response contains a message property', function () { + it('should return an ApiResponse object with a message when request is unauthorized and the response contains a message property', async function () { const response: AxiosResponse = { data: { message: 'Unauthorized.', @@ -153,7 +153,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode'); @@ -164,7 +164,7 @@ describe('ApiResponseFactory', function () { expect(apiResponse.data).to.equal(null); }); - it('should return an ApiResponse object with a message when request is a bad request', function () { + it('should return an ApiResponse object with a message when request is a bad request', async function () { const response: AxiosResponse = { data: { field: 'Invalid input.', @@ -175,7 +175,7 @@ describe('ApiResponseFactory', function () { config: {} as InternalAxiosRequestConfig, }; - const apiResponse = ApiResponseFactory.getApiResponse(response); + const apiResponse = await ApiResponseFactory.getApiResponse(response); expect(apiResponse).to.not.be.undefined; expect(apiResponse).to.have.property('statusCode');