Skip to content
Closed
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
2 changes: 1 addition & 1 deletion src/ _generated_/rest/api/records-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const RecordsApiAxiosParamCreator = function (configuration?: Configurati


if (fileColumnName !== undefined && fileColumnName.has('columnName')) {
localVarFormParams.append(`${fileColumnName.get('columnName')}`, new Blob([JSON.stringify(fileColumnName)], { type: "application/json", }));
localVarFormParams.append(`${fileColumnName.get('columnName')}`, fileColumnName.get('file') as File);
}


Expand Down
2 changes: 2 additions & 0 deletions src/error/codes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ const SKYFLOW_ERROR_CODE = {
INVALID_SKYFLOW_ID_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_SKYFLOW_ID_IN_UPLOAD_FILE },
MISSING_COLUMN_NAME_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_COLUMN_NAME_IN_UPLOAD_FILE },
INVALID_COLUMN_NAME_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_COLUMN_NAME_IN_UPLOAD_FILE },
INVALID_FILE_INPUT_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_FILE_INPUT_IN_UPLOAD_FILE },
MISSING_FILE_NAME_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_FILE_NAME_IN_UPLOAD_FILE },
MISSING_FILE_PATH_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_FILE_PATH_IN_UPLOAD_FILE },
INVALID_FILE_PATH_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_FILE_PATH_IN_UPLOAD_FILE },
INVALID_FILE_UPLOAD_REQUEST: { http_code: 400, message: errorMessages.INVALID_FILE_UPLOAD_REQUEST },
Expand Down
2 changes: 2 additions & 0 deletions src/error/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ const errorMessages = {
INVALID_SKYFLOW_ID_IN_UPLOAD_FILE: `${errorPrefix} Validation error. Invalid skyflow Id in file upload request. Specify a valid skyflow Id.`,
MISSING_COLUMN_NAME_IN_UPLOAD_FILE: `${errorPrefix} Validation error. Column name cannot be empty in file upload request. Specify a valid column name as string.`,
INVALID_COLUMN_NAME_IN_UPLOAD_FILE: `${errorPrefix} Validation error. Invalid column name in file upload request. Specify a valid column name.`,
INVALID_FILE_INPUT_IN_UPLOAD_FILE: `${errorPrefix} Validation error. Provide exactly one of filePath, base64, or fileObject.`,
MISSING_FILE_NAME_IN_UPLOAD_FILE: `${errorPrefix} Validation error. File name is required when providing a base64 string`,
MISSING_FILE_PATH_IN_UPLOAD_FILE: `${errorPrefix} Validation error. File path cannot be empty in file upload request. Specify a valid file path as string.`,
INVALID_FILE_PATH_IN_UPLOAD_FILE: `${errorPrefix} Validation error. Invalid file path in file upload request. Specify a valid file path.`,

Expand Down
14 changes: 11 additions & 3 deletions src/utils/validations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,11 +889,19 @@ export const validateUploadFileRequest = (fileRequest: FileUploadRequest, logLev
}


if (!fileRequest?.filePath || !Object.prototype.hasOwnProperty.call(fileRequest, '_filePath')) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_FILE_PATH_IN_UPLOAD_FILE);
if ((!fileRequest?.filePath && !fileRequest?.base64 && !fileRequest?.fileObject) || (fileRequest?.filePath && fileRequest?.base64) || (fileRequest?.filePath && fileRequest?.fileObject) || (fileRequest?.base64 && fileRequest?.fileObject)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_INPUT_IN_UPLOAD_FILE);
}

if (typeof fileRequest?.filePath !== 'string' || fileRequest?.filePath.trim().length === 0 || !isValidPath(fileRequest.filePath)) {
if (fileRequest?.base64 && !fileRequest?.fileName) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_FILE_NAME_IN_UPLOAD_FILE);
}

if (fileRequest?.fileObject && !fileRequest?.fileObject?.name) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_FILE_NAME_IN_UPLOAD_FILE);
}

if (fileRequest?.filePath && (typeof fileRequest?.filePath !== 'string' || fileRequest?.filePath.trim().length === 0 || !isValidPath(fileRequest.filePath))) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH_IN_UPLOAD_FILE);
}
} else {
Expand Down
28 changes: 25 additions & 3 deletions src/vault/controller/vault/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//imports
import * as fs from 'fs';
import path from 'path';
import InsertRequest from "../../model/request/insert";
import { BatchRecordMethod, QueryServiceExecuteQueryBody, RecordServiceBatchOperationBody, RecordServiceBulkDeleteRecordBody, RecordServiceInsertRecordBody, RecordServiceUpdateRecordBody, V1BYOT, V1DetokenizePayload, V1DetokenizeRecordRequest, V1FieldRecords, V1TokenizePayload, V1TokenizeRecordRequest } from "../../../ _generated_/rest";
import InsertOptions from "../../model/options/insert";
Expand Down Expand Up @@ -398,9 +399,30 @@ class VaultController {

//handle file exits
const formData = new FormData();
const fileStream = fs.createReadStream(request.filePath) as unknown as Blob;
formData.append('file', fileStream);
formData.append('columnName', request.columnName);
let fileBlob: Blob | File | undefined;
let fileName: string | undefined;

if(request.filePath){
const fileBuffer = fs.readFileSync(request.filePath);
fileName = path.basename(request.filePath);
fileBlob = new Blob([fileBuffer], { type: 'application/json' });
}

else if (request.base64) {
const buffer = Buffer.from(request.base64, 'base64');
fileName = request.fileName;
fileBlob = new Blob([buffer], { type: 'application/json' });
}

else if ((request.fileObject as Blob) instanceof Blob || (request.fileObject as File) instanceof File) {
fileBlob = request.fileObject;
fileName = request.fileObject?.name || "uploadedFile";
}

if (fileBlob && fileName) {
formData.append('file', fileBlob, fileName);
formData.append('columnName', request.columnName);
}

this.handleRequest(
(headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.fileServiceUploadFile(
Expand Down
54 changes: 34 additions & 20 deletions src/vault/model/request/file-upload/index.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,74 @@
//imports
// Imports

class FileUploadRequest {

//fields
private _tableName: string;
private _skyflowId: string;
private _columnName: string;
private _filePath: string;
private _filePath?: string;
private _base64?: string;
private _fileObject?: File;
private _fileName?: string;

// Constructor
constructor(tableName: string, skyflowId: string, columnName: string, filePath: string) {
constructor(tableName: string, skyflowId: string, columnName: string, filePath?: string, base64?: string, fileObject?: File, fileName?: string) {
this._tableName = tableName;
this._skyflowId = skyflowId;
this._columnName = columnName;
this._filePath = filePath;
}
this._base64 = base64;
this._fileObject = fileObject;
this._fileName = fileName;
}

// Getter for tableName
// Getters and Setters
public get tableName(): string {
return this._tableName;
}

// Setter for tableName
public set tableName(value: string) {
this._tableName = value;
}

// Getter for skyflowId
public get skyflowId(): string {
return this._skyflowId;
}

// Setter for skyflowId
public set skyflowId(value: string) {
this._skyflowId = value;
}

// Getter for columnName
public get columnName(): string {
return this._columnName;
}

// Setter for columnName
public set columnName(value: string) {
this._columnName = value;
}

// Getter for filePath
public get filePath(): string {
public get filePath(): string | undefined {
return this._filePath;
}

// Setter for filePath
public set filePath(value: string) {
public set filePath(value: string | undefined) {
this._filePath = value;
}

public get base64(): string | undefined {
return this._base64;
}
public set base64(value: string | undefined) {
this._base64 = value;
}

public get fileObject(): File | undefined {
return this._fileObject;
}
public set fileObject(value: File | undefined) {
this._fileObject = value;
}

public get fileName(): string | undefined {
return this._fileName;
}
public set fileName(value: string | undefined) {
this._fileName = value;
}
}

export default FileUploadRequest;
94 changes: 70 additions & 24 deletions test/vault/controller/vault.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import GetRequest from '../../../src/vault/model/request/get';
import GetColumnRequest from '../../../src/vault/model/request/get-column';
import SkyflowError from '../../../src/error';
import * as fs from 'fs';
import path from 'path';
import { FileUploadRequest } from '../../../src';

jest.mock('fs');

Expand Down Expand Up @@ -1121,22 +1123,54 @@ describe('VaultController uploadFile method', () => {
jest.clearAllMocks();
});

test('should successfully upload file', async () => {
const mockRequest = {
filePath: '/path/to/file',
columnName: 'testColumn',
tableName: 'testTable',
skyflowId: 'id123',
};
const mockResponseData = {data:{ skyflow_id: 'id123' }};
test('should successfully upload file from filePath', async () => {
const mockFilePath = '/path/to/file.json';
const mockFileBuffer = Buffer.from('mock file content');
const fileName = path.basename(mockFilePath);

jest.spyOn(fs, 'readFileSync').mockReturnValue(mockFileBuffer);

const mockStream = { on: jest.fn() };
jest.spyOn(mockFs, 'createReadStream').mockReturnValueOnce(mockStream);
const mockRequest = new FileUploadRequest('testTable', 'id123', 'testColumn', mockFilePath);
const mockResponseData = { data: { skyflow_id: 'id123' } };

mockVaultClient.vaultAPI.fileServiceUploadFile.mockResolvedValueOnce(mockResponseData);

const response = await vaultController.uploadFile(mockRequest);

expect(mockFs.createReadStream).toHaveBeenCalledWith(mockRequest.filePath);
expect(fs.readFileSync).toHaveBeenCalledWith(mockFilePath);
expect(response).toBeInstanceOf(FileUploadResponse);
expect(response.skyflowId).toBe('id123');
expect(response.errors).toHaveLength(0);
});

test('should successfully upload file from base64 string', async () => {
const mockBase64 = Buffer.from('mock file content').toString('base64');
const mockBuffer = Buffer.from(mockBase64, 'base64');

jest.spyOn(Buffer, 'from').mockReturnValue(mockBuffer);

const mockRequest = new FileUploadRequest('testTable', 'id123', 'testColumn', undefined, mockBase64, undefined, 'file.json');
const mockResponseData = { data: { skyflow_id: 'id123' } };

mockVaultClient.vaultAPI.fileServiceUploadFile.mockResolvedValueOnce(mockResponseData);

const response = await vaultController.uploadFile(mockRequest);

expect(Buffer.from).toHaveBeenCalledWith(mockBase64, 'base64');
expect(response).toBeInstanceOf(FileUploadResponse);
expect(response.skyflowId).toBe('id123');
expect(response.errors).toHaveLength(0);
});

test('should successfully upload file from fileObject', async () => {
const mockFile = new File([Buffer.from('mock file content')], 'testFile.json', { type: 'application/json' });

const mockRequest = new FileUploadRequest('testTable', 'id123', 'testColumn', undefined, undefined, mockFile);
const mockResponseData = { data: { skyflow_id: 'id123' } };

mockVaultClient.vaultAPI.fileServiceUploadFile.mockResolvedValueOnce(mockResponseData);

const response = await vaultController.uploadFile(mockRequest);

expect(response).toBeInstanceOf(FileUploadResponse);
expect(response.skyflowId).toBe('id123');
Expand All @@ -1160,6 +1194,24 @@ describe('VaultController uploadFile method', () => {
expect(mockVaultClient.vaultAPI.fileServiceUploadFile).not.toHaveBeenCalled();
});

test('should handle file read failure', async () => {
const mockFilePath = '/invalid/path/to/file.json';
const mockRequest = new FileUploadRequest('testTable', 'id123', 'testColumn', mockFilePath);

jest.spyOn(fs, 'readFileSync').mockImplementation(() => {
throw new Error('File read error');
});

await expect(vaultController.uploadFile(mockRequest)).rejects.toThrow('File read error');
});








test('should handle file stream creation failure', async () => {
const mockRequest = {
filePath: '/path/to/nonexistent/file',
Expand All @@ -1178,22 +1230,16 @@ describe('VaultController uploadFile method', () => {
});

test('should handle API errors during file upload', async () => {
const mockRequest = {
filePath: '/path/to/file',
columnName: 'testColumn',
tableName: 'testTable',
skyflowId: 'id123',
};
const mockStream = { on: jest.fn() };
jest.spyOn(mockFs, 'createReadStream').mockReturnValueOnce(mockStream);
validateUploadFileRequest.mockImplementation(() => {
// throw new Error('Validation error');
});
const errorResponse = new Error('Validation error');
const mockFilePath = '/path/to/file.json';
const mockRequest = new FileUploadRequest('testTable', 'id123', 'testColumn', mockFilePath);

jest.spyOn(fs, 'readFileSync').mockReturnValue(Buffer.from('mock file content'));

const errorResponse = new Error('Invalid');
mockVaultClient.vaultAPI.fileServiceUploadFile.mockRejectedValueOnce(errorResponse);

await expect(vaultController.uploadFile(mockRequest)).rejects.toEqual(errorResponse);
expect(mockVaultClient.vaultAPI.fileServiceUploadFile).not.toHaveBeenCalled();
expect(mockVaultClient.vaultAPI.fileServiceUploadFile).toHaveBeenCalled();
});

test('should log and reject errors during file upload', async () => {
Expand Down
Loading