diff --git a/.eslintrc.json b/.eslintrc.json index 2a0df09..a64089a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,8 @@ "files": ["tests/**/*.spec.ts"], "rules": { "@typescript-eslint/no-unused-expressions": "off", - "@typescript-eslint/consistent-type-assertions": "off" + "@typescript-eslint/consistent-type-assertions": "off", + "no-new": "off" } } ], diff --git a/src/models/ApiResponse.ts b/src/models/ApiResponse.ts index e179b06..9965142 100644 --- a/src/models/ApiResponse.ts +++ b/src/models/ApiResponse.ts @@ -2,6 +2,7 @@ import { App } from './App'; import { CollectionResponse } from './CollectionResponse'; import { CreatedWithIdResponse } from './CreatedWithIdResponse'; import { Field } from './Field'; +import { FileInfo } from './FileInfo'; import { GetPagedAppsResponse } from './GetPagedAppsResponse'; import { GetPagedFieldsResponse } from './GetPagedFieldsResponse'; @@ -218,4 +219,25 @@ export class ApiResponse { createdWithIdResponse ); } + + asFileInfoType(): ApiResponse { + const apiResponse = this as ApiResponse; + + const fileInfo = new FileInfo( + apiResponse.data.type, + apiResponse.data.contentType, + apiResponse.data.name, + apiResponse.data.createdDate, + apiResponse.data.modifiedDate, + apiResponse.data.owner, + apiResponse.data.notes, + apiResponse.data.fileHref + ); + + return new ApiResponse( + apiResponse.statusCode, + apiResponse.message, + fileInfo + ); + } } diff --git a/src/models/FileInfo.ts b/src/models/FileInfo.ts new file mode 100644 index 0000000..c333349 --- /dev/null +++ b/src/models/FileInfo.ts @@ -0,0 +1,36 @@ +import { FieldType } from '../enums/FieldType'; + +export class FileInfo { + public type: FieldType; + public contentType: string; + public name: string; + public createdDate: Date; + public modifiedDate: Date; + public owner: string; + public notes: string; + public fileHref: string; + + constructor( + type: string, + contentType: string, + name: string, + createdDate: Date, + modifiedDate: Date, + owner: string, + notes: string, + fileHref: string + ) { + if (FieldType[type] === undefined) { + throw new Error(`The type '${type}' is not a valid FieldType.`); + } + + this.type = FieldType[type]; + this.contentType = contentType; + this.name = name; + this.createdDate = createdDate; + this.modifiedDate = modifiedDate; + this.owner = owner; + this.notes = notes; + this.fileHref = fileHref; + } +} diff --git a/src/models/OnspringClient.ts b/src/models/OnspringClient.ts index 85deb94..96dd540 100644 --- a/src/models/OnspringClient.ts +++ b/src/models/OnspringClient.ts @@ -12,6 +12,7 @@ import { type Field } from './Field'; import { type GetPagedFieldsResponse } from './GetPagedFieldsResponse'; import { type SaveFileRequest } from './SaveFileRequest'; import { type CreatedWithIdResponse } from './CreatedWithIdResponse'; +import { type FileInfo } from './FileInfo'; /** * @class OnspringClient - A client that can communicate with the Onspring API. @@ -104,7 +105,8 @@ export class OnspringClient { appIds: number[] ): Promise>> { const endpoint = EndpointFactory.getAppsByIdsEndpoint(); - const apiResponse = await this.post(endpoint, appIds); + const uniqueIds = [...new Set(appIds)]; + const apiResponse = await this.post(endpoint, uniqueIds); if (apiResponse.isSuccessful === false) { return apiResponse; @@ -171,6 +173,33 @@ export class OnspringClient { return apiResponse.asGetPagedFieldsResponseType(); } + /** + * @method getFileInfoById - Gets a file's information by its id. + * @param {number} recordId - The id of the record that the file is attached to. + * @param {number} fieldId - The id of the field that the file is attached to. + * @param {number} fileId - The id of the file to get the information for. + * @returns {Promise>} - A promise that resolves to an ApiResponse of type FileInfo. + */ + public async getFileInfoById( + recordId: number, + fieldId: number, + fileId: number + ): Promise> { + const endpoint = EndpointFactory.getFileInfoByIdEndpoint( + recordId, + fieldId, + fileId + ); + + const apiResponse = await this.get(endpoint); + + if (apiResponse.isSuccessful === false) { + return apiResponse; + } + + return apiResponse.asFileInfoType(); + } + /** * @method saveFile - Saves a file to a record in Onspring. * @param {SaveFileRequest} request - The request that will be used to save the file. diff --git a/tests/FileInfo.spec.ts b/tests/FileInfo.spec.ts new file mode 100644 index 0000000..22c1d9d --- /dev/null +++ b/tests/FileInfo.spec.ts @@ -0,0 +1,190 @@ +import { FileInfo } from '../src/models/FileInfo'; +import { expect } from 'chai'; + +describe('FileInfo', function () { + it('should be defined', function () { + expect(FileInfo).to.not.be.undefined; + }); + + it('should have a constructor', function () { + expect(FileInfo).to.have.property('constructor'); + }); + + it('should have 8 parameters', function () { + expect(FileInfo).to.have.lengthOf(8); + }); + + it('should create a new instance of FileInfo', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.be.an.instanceOf(FileInfo); + }); + + it('should throw an error when the type is not a valid FieldType', function () { + expect(() => { + new FileInfo( + 'InvalidType', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + }).to.throw("The type 'InvalidType' is not a valid FieldType."); + }); + + it('should have a type property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('type'); + }); + + it('should have a contentType property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('contentType'); + }); + + it('should have a name property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('name'); + }); + + it('should have a createdDate property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('createdDate'); + }); + + it('should have a modifiedDate property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('modifiedDate'); + }); + + it('should have a owner property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('owner'); + }); + + it('should have a notes property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('notes'); + }); + + it('should have a fileHref property', function () { + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + new Date(), + new Date(), + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo).to.have.property('fileHref'); + }); + + it('should set properties to the correct values passed to the constructor', function () { + const date = new Date(); + + const fileInfo = new FileInfo( + 'Attachment', + 'application/pdf', + 'FileName.pdf', + date, + date, + 'File Owner', + 'notes', + 'https://api.onspring.dev' + ); + + expect(fileInfo.type).to.equal('Attachment'); + expect(fileInfo.contentType).to.equal('application/pdf'); + expect(fileInfo.name).to.equal('FileName.pdf'); + expect(fileInfo.createdDate).to.be.deep.equal(date); + expect(fileInfo.modifiedDate).to.be.deep.equal(date); + expect(fileInfo.owner).to.equal('File Owner'); + expect(fileInfo.notes).to.equal('notes'); + expect(fileInfo.fileHref).to.equal('https://api.onspring.dev'); + }); +}); diff --git a/tests/OnspringClient.spec.ts b/tests/OnspringClient.spec.ts index 73180c3..53dff45 100644 --- a/tests/OnspringClient.spec.ts +++ b/tests/OnspringClient.spec.ts @@ -16,6 +16,7 @@ import { GetPagedFieldsResponse } from '../src/models/GetPagedFieldsResponse'; import { SaveFileRequest } from '../src/models/SaveFileRequest'; import { Readable } from 'stream'; import { CreatedWithIdResponse } from '../src/models/CreatedWithIdResponse'; +import { FileInfo } from '../src/models/FileInfo'; describe('OnspringClient', function () { const baseUrl = 'https://api.onspring.dev'; @@ -1495,4 +1496,214 @@ describe('OnspringClient', function () { expect(result.data).to.be.null; }); }); + + describe('getFileInfoById', function () { + it('should be defined', function () { + expect(OnspringClient.prototype.getFileInfoById).to.be.not.undefined; + }); + + it('should be a function', function () { + expect(OnspringClient.prototype.getFileInfoById).to.be.a('function'); + }); + + it('should return a promise', function () { + expect( + new OnspringClient(baseUrl, apiKey).getFileInfoById(1, 1, 1) + ).to.be.instanceOf(Promise); + }); + + it('should return a promise that resolves to an api response when request is successful', async function () { + const client = new OnspringClient(baseUrl, apiKey); + + const mockAxiosClient = axios.create({ + baseURL: baseUrl, + headers: { + 'x-apikey': apiKey, + 'x-api-version': '2', + }, + }); + + sinon.stub(mockAxiosClient, 'get').returns( + Promise.resolve({ + status: 200, + statusText: 'OK', + data: { + type: 'Attachment', + contentType: 'string', + name: 'string', + createdDate: '2023-02-04T20:15:45.007Z', + modifiedDate: '2023-02-04T20:15:45.007Z', + owner: 'string', + notes: 'string', + fileHref: 'string', + }, + headers: {}, + config: {} as InternalAxiosRequestConfig, + } as AxiosResponse) + ); + + sinon.stub(client, '_client' as any).value(mockAxiosClient); + + const result = await client.getFileInfoById(1, 1, 1); + + expect(result).to.be.instanceOf(ApiResponse); + expect(result).to.have.property('statusCode', 200); + expect(result).to.have.property('isSuccessful', true); + expect(result.message).to.be.equal(''); + expect(result.data).to.be.not.null; + expect(result.data).to.be.instanceOf(FileInfo); + expect(result.data).to.have.property('type', 'Attachment'); + expect(result.data).to.have.property('contentType', 'string'); + expect(result.data).to.have.property('name', 'string'); + expect(result.data).to.have.property( + 'createdDate', + '2023-02-04T20:15:45.007Z' + ); + expect(result.data).to.have.property( + 'modifiedDate', + '2023-02-04T20:15:45.007Z' + ); + expect(result.data).to.have.property('owner', 'string'); + expect(result.data).to.have.property('notes', 'string'); + expect(result.data).to.have.property('fileHref', 'string'); + }); + + it('should return a promise that resolves to an api response when request receives a 400 response', async function () { + const client = new OnspringClient(baseUrl, apiKey); + + const mockAxiosClient = axios.create({ + baseURL: baseUrl, + headers: { + 'x-apikey': apiKey, + 'x-api-version': '2', + }, + }); + + sinon.stub(mockAxiosClient, 'get').returns( + Promise.resolve({ + status: 400, + statusText: 'Bad Request', + data: { + field: ['Field requested is not a file type field.'], + }, + headers: {}, + config: {} as InternalAxiosRequestConfig, + } as AxiosResponse) + ); + + sinon.stub(client, '_client' as any).value(mockAxiosClient); + + const result = await client.getFileInfoById(1, 1, 1); + + expect(result).to.be.instanceOf(ApiResponse); + expect(result).to.have.property('statusCode', 400); + expect(result).to.have.property('isSuccessful', false); + expect(result.message).to.equal( + '{"field":["Field requested is not a file type field."]}' + ); + expect(result.data).to.be.null; + }); + + it('should return a promise that resolves to an api response when request receives a 401 response', async function () { + const client = new OnspringClient(baseUrl, apiKey); + + const mockAxiosClient = axios.create({ + baseURL: baseUrl, + headers: { + 'x-apikey': apiKey, + 'x-api-version': '2', + }, + }); + + sinon.stub(mockAxiosClient, 'get').returns( + Promise.resolve({ + status: 401, + statusText: 'Unauthorized', + headers: {}, + config: {} as InternalAxiosRequestConfig, + } as AxiosResponse) + ); + + sinon.stub(client, '_client' as any).value(mockAxiosClient); + + const result = await client.getFileInfoById(1, 1, 1); + + expect(result).to.be.instanceOf(ApiResponse); + expect(result).to.have.property('statusCode', 401); + expect(result).to.have.property('isSuccessful', false); + expect(result.message).to.be.undefined; + expect(result.data).to.be.null; + }); + + it('should return a promise that resolves to an api response when request receives a 403 response', async function () { + const client = new OnspringClient(baseUrl, apiKey); + + const mockAxiosClient = axios.create({ + baseURL: baseUrl, + headers: { + 'x-apikey': apiKey, + 'x-api-version': '2', + }, + }); + + sinon.stub(mockAxiosClient, 'get').returns( + Promise.resolve({ + status: 403, + statusText: 'Forbidden', + data: { + message: + 'The user does not have permission to access this resource.', + }, + headers: {}, + config: {} as InternalAxiosRequestConfig, + } as AxiosResponse) + ); + + sinon.stub(client, '_client' as any).value(mockAxiosClient); + + const result = await client.getFileInfoById(1, 1, 1); + + expect(result).to.be.instanceOf(ApiResponse); + expect(result).to.have.property('statusCode', 403); + expect(result).to.have.property('isSuccessful', false); + expect(result.message).to.equal( + 'The user does not have permission to access this resource.' + ); + expect(result.data).to.be.null; + }); + + it('should return a promise that resolves to an api response when request receives a 404 response', async function () { + const client = new OnspringClient(baseUrl, apiKey); + + const mockAxiosClient = axios.create({ + baseURL: baseUrl, + headers: { + 'x-apikey': apiKey, + 'x-api-version': '2', + }, + }); + + sinon.stub(mockAxiosClient, 'get').returns( + Promise.resolve({ + status: 404, + statusText: 'Not Found', + data: { + message: 'The requested resource was not found.', + }, + headers: {}, + config: {} as InternalAxiosRequestConfig, + } as AxiosResponse) + ); + + sinon.stub(client, '_client' as any).value(mockAxiosClient); + + const result = await client.getFileInfoById(1, 1, 1); + + expect(result).to.be.instanceOf(ApiResponse); + expect(result).to.have.property('statusCode', 404); + expect(result).to.have.property('isSuccessful', false); + expect(result.message).to.equal('The requested resource was not found.'); + expect(result.data).to.be.null; + }); + }); }); diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 56142a4..8ab4792 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -1,8 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "commonjs", - "target": "es2015", + "module": "CommonJS", "outDir": "./dist/cjs" } } diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 55b5cad..72f3b9e 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -1,8 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "module": "esnext", - "target": "es2015", + "module": "ES6", "outDir": "./dist/esm" } } diff --git a/tsconfig.json b/tsconfig.json index d344dd5..c3cca18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { "strictNullChecks": true, - "moduleResolution": "node", "declaration": true, "declarationDir": "./dist/types", - "esModuleInterop": true + "esModuleInterop": true, + "target": "ES6", + "moduleResolution": "node" }, "include": ["src/**/*"], "ts-node": {