diff --git a/package-lock.json b/package-lock.json index df104a7..9d51928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "axios": "^1.6.1", "dotenv": "^16.4.5", "jsonwebtoken": "^9.0.2", - "jwt-decode": "^4.0.0" + "jwt-decode": "^2.2.0" }, "devDependencies": { "@babel/plugin-proposal-decorators": "^7.25.7", @@ -2955,9 +2955,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", - "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -2974,10 +2974,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001663", - "electron-to-chromium": "^1.5.28", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -3387,9 +3387,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", - "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "version": "1.5.42", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.42.tgz", + "integrity": "sha512-gIfKavKDw1mhvic9nbzA5lZw8QSHpdMwLwXc0cWidQz9B15pDoDdDH4boIatuFfeoCatb3a/NGL6CYRVFxGZ9g==", "dev": true }, "node_modules/emittery": { @@ -5704,12 +5704,9 @@ } }, "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "engines": { - "node": ">=18" - } + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", + "integrity": "sha512-86GgN2vzfUu7m9Wcj63iUkuDzFNYFVmjeDm2GzWpUk+opB0pEpMsw6ePCMrhYkumz2C1ihqtZzOMAg7FiXcNoQ==" }, "node_modules/kleur": { "version": "3.0.3", diff --git a/package.json b/package.json index 904c997..df76276 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "skyflow-node", "version": "1.14.0", "description": "Skyflow SDK for Node.js", - "main": "./lib/index.js", - "module": "./lib/index.js", + "main": "./lib/src/index.js", + "module": "./lib/src/index.js", "scripts": { "test": "jest --coverage", "build": "tsc", @@ -35,7 +35,7 @@ "axios": "^1.6.1", "dotenv": "^16.4.5", "jsonwebtoken": "^9.0.2", - "jwt-decode": "^4.0.0" + "jwt-decode": "^2.2.0" }, "devDependencies": { "@babel/plugin-proposal-decorators": "^7.25.7", diff --git a/src/error/codes/index.ts b/src/error/codes/index.ts index 66eeca6..4be0c12 100644 --- a/src/error/codes/index.ts +++ b/src/error/codes/index.ts @@ -11,6 +11,16 @@ const SKYFLOW_ERROR_CODE = { EMPTY_CLUSTER_ID: { http_code: 400, message: errorMessages.EMPTY_CLUSTER_ID }, INVALID_CLUSTER_ID: { http_code: 400, message: errorMessages.INVALID_CLUSTER_ID }, + INVALID_BEARER_TOKEN: { http_code: 400, message: errorMessages.INVALID_BEARER_TOKEN }, + INVALID_PARSED_CREDENTIALS_STRING: { http_code: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING }, + INVALID_API_KEY: { http_code: 400, message: errorMessages.INVALID_API_KEY }, + INVALID_FILE_PATH: { http_code: 400, message: errorMessages.INVALID_FILE_PATH }, + + INVALID_BEARER_TOKEN_WITH_ID: { http_code: 400, message: errorMessages.INVALID_BEARER_TOKEN_WITH_ID }, + INVALID_PARSED_CREDENTIALS_STRING_WITH_ID: { http_code: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID }, + INVALID_API_KEY_WITH_ID: { http_code: 400, message: errorMessages.INVALID_API_KEY_WITH_ID }, + INVALID_FILE_PATH_WITH_ID: { http_code: 400, message: errorMessages.INVALID_FILE_PATH_WITH_ID }, + INVALID_TOKEN: { http_code: 400, message: errorMessages.INVALID_TOKEN }, TOKEN_EXPIRED: { http_code: 400, message: errorMessages.TOKEN_EXPIRED }, INVALID_ENV: { http_code: 400, message: errorMessages.INVALID_ENV }, diff --git a/src/error/messages/index.ts b/src/error/messages/index.ts index c26f269..ccc6960 100644 --- a/src/error/messages/index.ts +++ b/src/error/messages/index.ts @@ -18,6 +18,16 @@ const errorMessages = { INVALID_LOG_LEVEL: `${errorPrefix} Initialization failed. Invalid log level. Specify a valid log level.`, EMPTY_CREDENTIAL_FILE_PATH: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid file path.`, INVALID_CREDENTIAL_FILE_PATH: `${errorPrefix} Initialization failed. Invalid credentials. Expected file path to be a string.`, + + INVALID_FILE_PATH: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Expected file path to exists.`, + INVALID_API_KEY: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid api key.`, + INVALID_PARSED_CREDENTIALS_STRING: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid credentials string.`, + INVALID_BEARER_TOKEN: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid token.`, + + INVALID_FILE_PATH_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Expected file path to exists for %s1 with %s2 %s3.`, + INVALID_API_KEY_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid api key for %s1 with %s2 %s3.`, + INVALID_PARSED_CREDENTIALS_STRING_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid credentials string for %s1 with %s2 %s3.`, + INVALID_BEARER_TOKEN_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid token for %s1 with %s2 %s3.`, EMPTY_CONNECTION_ID: `${errorPrefix} Initialization failed. Invalid connection ID. Specify a valid connection Id.`, INVALID_CONNECTION_ID: `${errorPrefix} Initialization failed. Invalid connection ID. Specify connection Id as a string.`, @@ -162,6 +172,8 @@ const errorMessages = { INVALID_ORDER_BY: `${errorPrefix} Validation error. The orderBy key has a value of type %s1. Specify orderBy as string.`, INVALID_FIELDS: `${errorPrefix} Validation error. The fields key has a value of type %s1. Specify fields as array of strings.`, + + INVAILD_JSON_RESPONSE: `${errorPrefix} Validation error. The invalid json response. Please reach out to skyflow using requestId - %s1.`, }; export default errorMessages; \ No newline at end of file diff --git a/src/utils/jwt-utils/index.ts b/src/utils/jwt-utils/index.ts index 10a3d2d..7ffacf6 100644 --- a/src/utils/jwt-utils/index.ts +++ b/src/utils/jwt-utils/index.ts @@ -1,4 +1,4 @@ -import { JwtPayload, jwtDecode } from 'jwt-decode'; +import jwt_decode, { JwtPayload } from 'jwt-decode'; import { MessageType, printLog } from '..'; import logs from '../logs'; @@ -13,7 +13,7 @@ function isExpired(token: string) { return true } let isJwtExpired = false; - const decoded: JwtPayload = jwtDecode(token); + const decoded: JwtPayload = jwt_decode(token); const currentTime = (new Date().getTime() / 1000); const expiryTime = decoded.exp; if (expiryTime && currentTime > expiryTime) { @@ -26,7 +26,7 @@ function isExpired(token: string) { function isTokenValid(token: string) { if (token === "") return false let isJwtExpired = false; - const decoded: JwtPayload = jwtDecode(token); + const decoded: JwtPayload = jwt_decode(token); const currentTime = (new Date().getTime() / 1000); const expiryTime = decoded.exp; if (expiryTime && currentTime > expiryTime) { diff --git a/src/utils/validations/index.ts b/src/utils/validations/index.ts index 1d8bce8..f5ece03 100644 --- a/src/utils/validations/index.ts +++ b/src/utils/validations/index.ts @@ -20,6 +20,8 @@ import QueryRequest from "../../vault/model/request/query"; import TokenizeRequest from "../../vault/model/request/tokenize"; import UpdateRequest from "../../vault/model/request/update"; import { SkyflowConfig, StringKeyValueMapType } from "../../vault/types"; +import { isValid } from "../jwt-utils"; +import * as fs from 'fs'; export function isEnv(value?: string): boolean { return value !== undefined && Object.values(Env).includes(value as Env); @@ -41,6 +43,45 @@ export function isLogLevel(value?: string): boolean { return value !== undefined && Object.values(LogLevel).includes(value as LogLevel); } +function isValidAPIKey(apiKey: string) { + if(!apiKey || apiKey===null || apiKey===undefined){ + return false; + } + if(apiKey && typeof apiKey === 'string' && apiKey.startsWith("sky-")){ + return true; + } + return false; +}; + +function isValidCredentialsString(credentialsString: string) { + if(!credentialsString || credentialsString===null || credentialsString===undefined){ + return false; + } + if(credentialsString && typeof credentialsString === 'string'){ + try { + let credentialsObj = JSON.parse("{}") + credentialsObj = JSON.parse(credentialsString); + if ( credentialsObj?.clientID === null || credentialsObj?.keyID === null || credentialsObj?.clientID === null) { + return false; + } + return true; + } catch(err) { + return false; + } + } + return false; +}; + +function isValidPath(path: string) { + if(!path || path===null || path===undefined){ + return false; + } + if(path && typeof path === 'string' && fs.existsSync(path)){ + return true; + } + return false; +}; + export const validateSkyflowConfig = (config: SkyflowConfig) => { if (config) { if (!Object.prototype.hasOwnProperty.call(config, 'vaultConfigs')) { @@ -82,6 +123,22 @@ export const validateCredentialsWithId = (credentials: Credentials, type: string throw new SkyflowError(SKYFLOW_ERROR_CODE.MULTIPLE_CREDENTIALS_PASSED_WITH_ID, [type, typeId, id]); } + if (credentials?.token && typeof credentials?.token !== 'string' && !isValid(credentials?.token)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN_WITH_ID, [type, typeId, id]); + } + + if (credentials?.credentialsString && typeof credentials?.credentialsString !== 'string' && !isValidCredentialsString(credentials?.credentialsString)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID, [type, typeId, id]); + } + + if (credentials?.apiKey && typeof credentials?.apiKey !== 'string' && !isValidAPIKey(credentials?.apiKey)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY_WITH_ID, [type, typeId, id]); + } + + if (credentials?.path && typeof credentials?.path !== 'string' && !isValidPath(credentials?.path)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH_WITH_ID, [type, typeId, id]); + } + }; export const validateVaultConfig = (vaultConfig: VaultConfig) => { @@ -105,6 +162,24 @@ export const validateVaultConfig = (vaultConfig: VaultConfig) => { } }; +export const validateUpdateVaultConfig = (vaultConfig: VaultConfig) => { + if (!Object.prototype.hasOwnProperty.call(vaultConfig, 'vaultId')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_VAULT_ID); + } + if (!vaultConfig?.vaultId || typeof vaultConfig?.vaultId !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_VAULT_ID); + } + if (vaultConfig?.clusterId && typeof vaultConfig.clusterId !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CLUSTER_ID, [vaultConfig?.vaultId]); + } + if (vaultConfig?.env && !isEnv(vaultConfig.env)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ENV, [vaultConfig?.vaultId]); + } + if (vaultConfig?.credentials) { + validateCredentialsWithId(vaultConfig.credentials, VAULT, VAULT_ID, vaultConfig.vaultId); + } +}; + export const validateSkyflowCredentials = (credentials: Credentials) => { const { token, path, credentialsString, apiKey } = credentials; @@ -121,6 +196,22 @@ export const validateSkyflowCredentials = (credentials: Credentials) => { throw new SkyflowError(SKYFLOW_ERROR_CODE.MULTIPLE_CREDENTIALS_PASSED); } + if (credentials?.token && typeof credentials?.token !== 'string' && !isValid(credentials?.token)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BEARER_TOKEN); + } + + if (credentials?.credentialsString && typeof credentials?.credentialsString !== 'string' && !isValidCredentialsString(credentials?.credentialsString)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PARSED_CREDENTIALS_STRING); + } + + if (credentials?.apiKey && typeof credentials?.apiKey !== 'string' && !isValidAPIKey(credentials?.apiKey)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_API_KEY); + } + + if (credentials?.path && typeof credentials?.path !== 'string' && !isValidPath(credentials?.path)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_PATH); + } + }; export const validateConnectionConfig = (connectionConfig: ConnectionConfig) => { @@ -149,6 +240,28 @@ export const validateConnectionConfig = (connectionConfig: ConnectionConfig) => } }; +export const validateUpdateConnectionConfig = (connectionConfig: ConnectionConfig) => { + if (!Object.prototype.hasOwnProperty.call(connectionConfig, 'connectionId')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_ID); + } + + if (typeof connectionConfig?.connectionId !== 'string' || connectionConfig?.connectionId.trim().length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONNECTION_ID); + } + + if (connectionConfig?.connectionUrl && (typeof connectionConfig?.connectionUrl !== 'string' || connectionConfig?.connectionUrl.trim().length === 0)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONNECTION_URL); + } + + if (connectionConfig?.connectionUrl && !isValidURL(connectionConfig.connectionUrl)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONNECTION_URL); + } + + if (connectionConfig?.credentials) { + validateCredentialsWithId(connectionConfig.credentials, CONNECTION, CONNECTION_ID, connectionConfig.connectionId); + } +}; + function validateInsertInput(input: unknown): void { try { const inputObject = input as { [key: string]: unknown }; diff --git a/src/vault/client/index.ts b/src/vault/client/index.ts index 3be9b83..7c69f2e 100644 --- a/src/vault/client/index.ts +++ b/src/vault/client/index.ts @@ -126,14 +126,17 @@ class VaultClient { }); private handleJsonError(err: any, data: any, requestId: string, reject: Function) { - //handle parsing - let description = JSON.parse(JSON.stringify(data)); - const statusCode = description?.error?.http_status; - const grpcCode = description?.error?.grpc_code; - const details = description?.error?.details; - - description = description?.error?.message || description; - this.logAndRejectError(description, err, requestId, reject, statusCode, grpcCode, details); + try { + let description = JSON.parse(JSON.stringify(data)); + const statusCode = description?.error?.http_status; + const grpcCode = description?.error?.grpc_code; + const details = description?.error?.details; + + description = description?.error?.message || description; + this.logAndRejectError(description, err, requestId, reject, statusCode, grpcCode, details); + } catch (err) { + this.logAndRejectError(errorMessages.INVAILD_JSON_RESPONSE, err, requestId, reject, 500, undefined, undefined); + } } private handleTextError(err: any, data: any, requestId: string, reject: Function) { diff --git a/src/vault/controller/vault/index.ts b/src/vault/controller/vault/index.ts index a2177a1..1409614 100644 --- a/src/vault/controller/vault/index.ts +++ b/src/vault/controller/vault/index.ts @@ -22,14 +22,12 @@ import FileUploadResponse from '../../model/response/file-upload'; import TokenizeResponse from '../../model/response/tokenize'; import TokenizeRequest from '../../model/request/tokenize'; import { ParsedDetokenizeResponse, ParsedInsertBatchResponse, tokenizeRequestType } from '../../types'; -import { generateSDKMetrics, getBearerToken, LogLevel, MessageType, parameterizedString, printLog, TYPES, SDK_METRICS_HEADER_KEY } from '../../../utils'; +import { generateSDKMetrics, getBearerToken, MessageType, parameterizedString, printLog, TYPES, SDK_METRICS_HEADER_KEY } from '../../../utils'; import GetColumnRequest from '../../model/request/get-column'; import logs from '../../../utils/logs'; import VaultClient from '../../client'; import { RawAxiosRequestConfig } from 'axios'; -import SkyflowError from '../../../error'; import { validateDeleteRequest, validateDetokenizeRequest, validateGetColumnRequest, validateGetRequest, validateInsertRequest, validateQueryRequest, validateTokenizeRequest, validateUpdateRequest, validateUploadFileRequest } from '../../../utils/validations'; -import errorMessages from '../../../error/messages'; class VaultController { @@ -202,316 +200,320 @@ class VaultController { insert(request: InsertRequest, options?: InsertOptions): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.INSERT_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_INSERT_INPUT, MessageType.LOG, this.client.getLogLevel()); - // validations checks try { + printLog(logs.infoLogs.INSERT_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_INSERT_INPUT, MessageType.LOG, this.client.getLogLevel()); + // validations checks validateInsertRequest(request, options); + + const isContinueOnError = options?.getContinueOnError(); + + const requestBody = isContinueOnError + ? this.buildBatchInsertBody(request, options) + : this.buildBulkInsertBody(request, options); + + + const operationType = isContinueOnError ? TYPES.INSERT_BATCH : TYPES.INSERT; + const tableName = request.tableName; + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => + isContinueOnError + ? this.client.vaultAPI.recordServiceBatchOperation(this.client.vaultId, requestBody, headers) + : this.client.vaultAPI.recordServiceInsertRecord(this.client.vaultId, tableName, requestBody as RecordServiceInsertRecordBody, headers), + operationType + ).then((resp: any) => { + const parsedResponse = isContinueOnError + ? this.parseInsertBatchResponse(resp) + : this.parseBulkInsertResponse(resp); + resolve(parsedResponse); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + reject(error); + }); } catch (error) { reject(error); } - const isContinueOnError = options?.getContinueOnError(); - - const requestBody = isContinueOnError - ? this.buildBatchInsertBody(request, options) - : this.buildBulkInsertBody(request, options); - - - const operationType = isContinueOnError ? TYPES.INSERT_BATCH : TYPES.INSERT; - const tableName = request.tableName; - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => - isContinueOnError - ? this.client.vaultAPI.recordServiceBatchOperation(this.client.vaultId, requestBody, headers) - : this.client.vaultAPI.recordServiceInsertRecord(this.client.vaultId, tableName, requestBody as RecordServiceInsertRecordBody, headers), - operationType - ).then((resp: any) => { - const parsedResponse = isContinueOnError - ? this.parseInsertBatchResponse(resp) - : this.parseBulkInsertResponse(resp); - resolve(parsedResponse); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - reject(error); - }); }); } update(request: UpdateRequest, options?: UpdateOptions): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.UPDATE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_UPDATE_INPUT, MessageType.LOG, this.client.getLogLevel()); - // Validation checks try { + printLog(logs.infoLogs.UPDATE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_UPDATE_INPUT, MessageType.LOG, this.client.getLogLevel()); + // Validation checks validateUpdateRequest(request, options); + + const record = { fields: request.updateData }; + const strictMode = options?.getTokenMode() ? V1BYOT.Enable : V1BYOT.Disable; + const updateData: RecordServiceUpdateRecordBody = { + record: record, + tokenization: options?.getReturnTokens(), + byot: strictMode + }; + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceUpdateRecord( + this.client.vaultId, + request.tableName, + request.skyflowId, + updateData, + headers + ), + TYPES.UPDATE + ).then(data => { + const updatedRecord = { + skyflowID: data.skyflow_id, + ...data?.tokens + }; + resolve(new UpdateResponse({ updatedField: updatedRecord, errors: [] })); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - const record = { fields: request.updateData }; - const strictMode = options?.getTokenMode() ? V1BYOT.Enable : V1BYOT.Disable; - const updateData: RecordServiceUpdateRecordBody = { - record: record, - tokenization: options?.getReturnTokens(), - byot: strictMode - }; - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceUpdateRecord( - this.client.vaultId, - request.tableName, - request.skyflowId, - updateData, - headers - ), - TYPES.UPDATE - ).then(data => { - const updatedRecord = { - skyflowID: data.skyflow_id, - ...data?.tokens - }; - resolve(new UpdateResponse({ updatedField: updatedRecord, errors: [] })); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } delete(request: DeleteRequest): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.DELETE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_DELETE_INPUT, MessageType.LOG, this.client.getLogLevel()); - - // Validation checks try { + printLog(logs.infoLogs.DELETE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_DELETE_INPUT, MessageType.LOG, this.client.getLogLevel()); + // Validation checks validateDeleteRequest(request); - } catch (error) { + + const deleteRequest: RecordServiceBulkDeleteRecordBody = { + skyflow_ids: request.deleteIds, + }; + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceBulkDeleteRecord( + this.client.vaultId, + request.tableName, + deleteRequest, + headers + ), + TYPES.DELETE + ).then(data => { + resolve(data); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + reject(error); + }); + } catch (error: any) { reject(error); } - - const deleteRequest: RecordServiceBulkDeleteRecordBody = { - skyflow_ids: request.deleteIds, - }; - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceBulkDeleteRecord( - this.client.vaultId, - request.tableName, - deleteRequest, - headers - ), - TYPES.DELETE - ).then(data => { - //add check - resolve(data); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - reject(error); - }); }); } get(request: GetRequest | GetColumnRequest, options?: GetOptions): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.GET_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_GET_INPUT, MessageType.LOG, this.client.getLogLevel()); - - // Validation checks try { + printLog(logs.infoLogs.GET_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_GET_INPUT, MessageType.LOG, this.client.getLogLevel()); + + // Validation checks if (request instanceof GetRequest) { validateGetRequest(request); } if (request instanceof GetColumnRequest) { validateGetColumnRequest(request); } + + + let records: Array = []; + let columnName: string = ""; + let columnValues: Array = []; + if (request instanceof GetRequest && request.ids) { + records = request.ids as Array; + } + if (request instanceof GetColumnRequest && request.columnName && request.columnValues) { + columnName = request.columnName as string; + columnValues = request.columnValues as Array; + } + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceBulkGetRecord( + this.client.vaultId, + request.tableName, + records, + options?.getRedactionType(), + options?.getReturnTokens(), + options?.getFields(), + options?.getOffset(), + options?.getLimit(), + options?.getDownloadURL(), + columnName, + columnValues, + options?.getOrderBy(), + headers + ), + TYPES.GET + ).then(records => { + const processedRecords = records.map(record => ({ + ...record.fields, + })); + resolve(new GetResponse({ data: processedRecords, errors: [] })); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - - let records: Array = []; - let columnName: string = ""; - let columnValues: Array = []; - if (request instanceof GetRequest && request.ids) { - records = request.ids as Array; - } - if (request instanceof GetColumnRequest && request.columnName && request.columnValues) { - columnName = request.columnName as string; - columnValues = request.columnValues as Array; - } - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.recordServiceBulkGetRecord( - this.client.vaultId, - request.tableName, - records, - options?.getRedactionType(), - options?.getReturnTokens(), - options?.getFields(), - options?.getOffset(), - options?.getLimit(), - options?.getDownloadURL(), - columnName, - columnValues, - options?.getOrderBy(), - headers - ), - TYPES.GET - ).then(records => { - const processedRecords = records.map(record => ({ - ...record.fields, - })); - resolve(new GetResponse({ data: processedRecords, errors: [] })); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } uploadFile(request: FileUploadRequest): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.UPLOAD_FILE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_FILE_UPLOAD_INPUT, MessageType.LOG, this.client.getLogLevel()); - - // Validation checks try { + printLog(logs.infoLogs.UPLOAD_FILE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_FILE_UPLOAD_INPUT, MessageType.LOG, this.client.getLogLevel()); + + // Validation checks validateUploadFileRequest(request); + + //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); + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.fileServiceUploadFile( + this.client.vaultId, + request.tableName, + request.skyflowId, + formData, + headers + ), + TYPES.FILE_UPLOAD + ).then(data => { + resolve(new FileUploadResponse({ skyflowID: data.skyflow_id, errors: [] })); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - //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); - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.vaultAPI.fileServiceUploadFile( - this.client.vaultId, - request.tableName, - request.skyflowId, - formData, - headers - ), - TYPES.FILE_UPLOAD - ).then(data => { - resolve(new FileUploadResponse({ skyflowID: data.skyflow_id, errors: [] })); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } query(request: QueryRequest): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.QUERY_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_QUERY_INPUT, MessageType.LOG, this.client.getLogLevel()); - - // Validation checks try { + printLog(logs.infoLogs.QUERY_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_QUERY_INPUT, MessageType.LOG, this.client.getLogLevel()); + + // Validation checks validateQueryRequest(request); + + const query: QueryServiceExecuteQueryBody = { + query: request.query, + }; + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.queryAPI.queryServiceExecuteQuery( + this.client.vaultId, + query, + headers + ), + TYPES.QUERY + ).then(records => { + const processedRecords = records.map(record => ({ + ...record?.fields, + tokenizedData: { + ...record?.tokens, + }, + })); + resolve(new QueryResponse({ fields: processedRecords, errors: [] })); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - - const query: QueryServiceExecuteQueryBody = { - query: request.query, - }; - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.queryAPI.queryServiceExecuteQuery( - this.client.vaultId, - query, - headers - ), - TYPES.QUERY - ).then(records => { - const processedRecords = records.map(record => ({ - ...record?.fields, - tokenizedData: { - ...record?.tokens, - }, - })); - resolve(new QueryResponse({ fields: processedRecords, errors: [] })); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } detokenize(request: DetokenizeRequest, options?: DetokenizeOptions): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.DETOKENIZE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_DETOKENIZE_INPUT, MessageType.LOG, this.client.getLogLevel()); - //validations checks try { + printLog(logs.infoLogs.DETOKENIZE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_DETOKENIZE_INPUT, MessageType.LOG, this.client.getLogLevel()); + + //validations checks validateDetokenizeRequest(request, options); + + const fields = request.tokens.map(record => ({ token: record, redaction: request?.redactionType })) as Array; + const detokenizePayload: V1DetokenizePayload = { detokenizationParameters: fields, continueOnError: options?.getContinueOnError(), downloadURL: options?.getDownloadURL() }; + + this.handleRequest( + (headers: RawAxiosRequestConfig | undefined) => this.client.tokensAPI.recordServiceDetokenize(this.client.vaultId, detokenizePayload, headers), + TYPES.DETOKENIZE + ).then(records => { + const parsedResponse: ParsedDetokenizeResponse = this.parseDetokenizeResponse(records); + resolve(new DetokenizeResponse({ detokenizedFields: parsedResponse.success, errors: parsedResponse.errors })); + }) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - - const fields = request.tokens.map(record => ({ token: record, redaction: request?.redactionType })) as Array; - const detokenizePayload: V1DetokenizePayload = { detokenizationParameters: fields, continueOnError: options?.getContinueOnError(), downloadURL: options?.getDownloadURL() }; - - this.handleRequest( - (headers: RawAxiosRequestConfig | undefined) => this.client.tokensAPI.recordServiceDetokenize(this.client.vaultId, detokenizePayload, headers), - TYPES.DETOKENIZE - ).then(records => { - const parsedResponse: ParsedDetokenizeResponse = this.parseDetokenizeResponse(records); - resolve(new DetokenizeResponse({ detokenizedFields: parsedResponse.success, errors: parsedResponse.errors })); - }) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } tokenize(request: TokenizeRequest): Promise { return new Promise((resolve, reject) => { - printLog(logs.infoLogs.TOKENIZE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); - printLog(logs.infoLogs.VALIDATE_TOKENIZE_INPUT, MessageType.LOG, this.client.getLogLevel()); - //validation checks try { + printLog(logs.infoLogs.TOKENIZE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); + printLog(logs.infoLogs.VALIDATE_TOKENIZE_INPUT, MessageType.LOG, this.client.getLogLevel()); + + //validation checks validateTokenizeRequest(request); + + const fields = request.values.map((record: tokenizeRequestType) => ({ value: record.value, columnGroup: record.columnGroup })) as Array; + const tokenizePayload: V1TokenizePayload = { tokenizationParameters: fields }; + + this.handleRequest( + () => this.client.tokensAPI.recordServiceTokenize(this.client.vaultId, tokenizePayload), + TYPES.TOKENIZE + ).then(records => resolve(new TokenizeResponse({ tokens: records, errors: [] }))) + .catch(error => { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); + // throw Skyflow Error + reject(error); + }); } catch (error) { reject(error); } - validateTokenizeRequest(request); - const fields = request.values.map((record: tokenizeRequestType) => ({ value: record.value, columnGroup: record.columnGroup })) as Array; - const tokenizePayload: V1TokenizePayload = { tokenizationParameters: fields }; - - this.handleRequest( - () => this.client.tokensAPI.recordServiceTokenize(this.client.vaultId, tokenizePayload), - TYPES.TOKENIZE - ).then(records => resolve(new TokenizeResponse({ tokens: records, errors: [] }))) - .catch(error => { - if (error instanceof Error) - printLog(error.message, MessageType.ERROR, this.client.getLogLevel()); - // throw Skyflow Error - reject(error); - }); }); } diff --git a/src/vault/skyflow/index.ts b/src/vault/skyflow/index.ts index f68c6d9..ae8e6c7 100644 --- a/src/vault/skyflow/index.ts +++ b/src/vault/skyflow/index.ts @@ -8,7 +8,7 @@ import VaultClient from "../client"; import Credentials from "../config/credentials"; import SkyflowError from "../../error"; import logs from "../../utils/logs"; -import { isLogLevel, validateConnectionConfig, validateSkyflowConfig, validateSkyflowCredentials, validateVaultConfig } from "../../utils/validations"; +import { isLogLevel, validateConnectionConfig, validateSkyflowConfig, validateSkyflowCredentials, validateUpdateConnectionConfig, validateUpdateVaultConfig, validateVaultConfig } from "../../utils/validations"; import SKYFLOW_ERROR_CODE from "../../error/codes"; class Skyflow { @@ -94,12 +94,12 @@ class Skyflow { } updateVaultConfig(config: VaultConfig) { - // validateVaultConfig(config); + validateUpdateVaultConfig(config); this.updateVaultClient(config, this.vaultClients, VAULT_ID); } updateConnectionConfig(config: ConnectionConfig) { - // validateConnectionConfig(config); + validateUpdateConnectionConfig(config); this.updateConnectionClient(config, this.connectionClients, CONNECTION_ID); } diff --git a/test/vault/client/client.test.js b/test/vault/client/client.test.js index 1639e2e..045ca45 100644 --- a/test/vault/client/client.test.js +++ b/test/vault/client/client.test.js @@ -254,7 +254,7 @@ describe('VaultClient', () => { }, }; vaultClient.failureResponse(errorResponse).catch(err => { - expect(err).toBeInstanceOf(SyntaxError); + expect(err).toBeInstanceOf(SkyflowError); }) }); diff --git a/test/vault/controller/vault.test.js b/test/vault/controller/vault.test.js index 14178e9..cd4b339 100644 --- a/test/vault/controller/vault.test.js +++ b/test/vault/controller/vault.test.js @@ -1,5 +1,5 @@ import VaultController from '../../../src/vault/controller/vault'; -import { printLog, parameterizedString, MessageType, TYPES } from '../../../src/utils'; +import { printLog, MessageType } from '../../../src/utils'; import logs from '../../../src/utils/logs'; import { validateInsertRequest, validateDetokenizeRequest, validateDeleteRequest, validateTokenizeRequest, validateQueryRequest, validateUpdateRequest, validateUploadFileRequest, validateGetRequest, validateGetColumnRequest } from '../../../src/utils/validations'; import InsertResponse from '../../../src/vault/model/response/insert'; @@ -123,7 +123,6 @@ describe('VaultController', () => { }); }); - describe('VaultController insert method', () => { let mockVaultClient; let vaultController; @@ -251,6 +250,52 @@ describe('VaultController insert method', () => { expect(response.insertedFields).toHaveLength(1); }); + test('should successfully insert records with batch insert with null record', async () => { + const mockRequest = { + data: [{ field1: 'value1' }], + tableName: 'testTable', + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsert: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]) + }; + const mockResponseData = { responses: [{ Body: { records: [{ skyflow_id: 'id123' }] }, Status: 200 }, null] }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockResolvedValueOnce({ data: mockResponseData }); + + const response = await vaultController.insert(mockRequest, mockOptions); + + expect(mockVaultClient.vaultAPI.recordServiceBatchOperation).toHaveBeenCalled(); + expect(response.insertedFields).toHaveLength(1); + }); + + test('should successfully insert records with batch insert with null response', async () => { + const mockRequest = { + data: [{ field1: 'value1' }], + tableName: 'testTable', + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsert: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]) + }; + const mockResponseData = null; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockResolvedValueOnce({ data: mockResponseData }); + + const response = await vaultController.insert(mockRequest, mockOptions); + + expect(mockVaultClient.vaultAPI.recordServiceBatchOperation).toHaveBeenCalled(); + expect(response.insertedFields).toBe(undefined); + }); + test('should reject insert records with batch insert', async () => { const mockRequest = { data: [{ field1: 'value1' }], @@ -293,7 +338,7 @@ describe('VaultController insert method', () => { await expect(vaultController.insert(mockRequest, mockOptions)).rejects.toThrow('Validation error'); expect(validateInsertRequest).toHaveBeenCalled(); - expect(mockVaultClient.vaultAPI.recordServiceInsertRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceInsertRecord).not.toHaveBeenCalled(); }); test('should log and reject on API error', async () => { @@ -328,7 +373,7 @@ describe('VaultController detokenize method', () => { }, getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}), + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})), }; vaultController = new VaultController(mockVaultClient); jest.clearAllMocks(); @@ -365,6 +410,63 @@ describe('VaultController detokenize method', () => { expect(response.errors).toHaveLength(1); // Error responses }); + test('should successfully detokenize records with different request', async () => { + const mockRequest = { + tokens: ['token1', 'token2'], + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadURL: jest.fn().mockReturnValue(true) + }; + const mockDetokenizeResponse = { + data: { + records: [ + { token: 'token1', value: 'value1' }, + { token: 'token2', error: 'error2' } + ] + } + }; + + mockVaultClient.tokensAPI.recordServiceDetokenize.mockResolvedValueOnce(mockDetokenizeResponse); + + const response = await vaultController.detokenize(mockRequest, mockOptions); + + expect(mockVaultClient.tokensAPI.recordServiceDetokenize).toHaveBeenCalledWith( + 'vault123', + expect.anything(), // Detokenization payload + expect.any(Object) // Headers + ); + expect(response.detokenizedFields).toHaveLength(1); // Success responses + expect(response.errors).toHaveLength(1); // Error responses + }); + + test('should successfully detokenize records with empty options', async () => { + const mockRequest = { + tokens: ['token1', 'token2'], + }; + + const mockDetokenizeResponse = { + data: { + records: [ + { token: 'token1', value: 'value1' }, + { token: 'token2', error: 'error2' } + ] + } + }; + + mockVaultClient.tokensAPI.recordServiceDetokenize.mockResolvedValueOnce(mockDetokenizeResponse); + + const response = await vaultController.detokenize(mockRequest); + + expect(mockVaultClient.tokensAPI.recordServiceDetokenize).toHaveBeenCalledWith( + 'vault123', + expect.anything(), // Detokenization payload + expect.any(Object) // Headers + ); + expect(response.detokenizedFields).toHaveLength(1); // Success responses + expect(response.errors).toHaveLength(1); // Error responses + }); + test('should return unknown detokenize records', async () => { const mockRequest = { tokens: ['token1', 'token2'], @@ -413,19 +515,18 @@ describe('VaultController detokenize method', () => { test('should handle API error during detokenize', async () => { const mockRequest = { - tokens: ['token1', 'token2'], - redactionType: 'PLAIN_TEXT', + tokens: ['token1', 'token2'] }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), getDownloadURL: jest.fn().mockReturnValue(false) }; - const errorResponse = new Error("Validation error"); + const errorResponse = new Error("Invalid"); mockVaultClient.tokensAPI.recordServiceDetokenize.mockRejectedValueOnce(errorResponse); await expect(vaultController.detokenize(mockRequest, mockOptions)).rejects.toThrow('Validation error'); - expect(mockVaultClient.tokensAPI.recordServiceDetokenize).toHaveBeenCalled(); + expect(mockVaultClient.tokensAPI.recordServiceDetokenize).not.toHaveBeenCalled(); }); test('should log and resolve with empty arrays when no records are returned', async () => { @@ -442,7 +543,7 @@ describe('VaultController detokenize method', () => { // throw new Error('Validation error'); }); - mockVaultClient.tokensAPI.recordServiceDetokenize.mockResolvedValueOnce([]); + mockVaultClient.tokensAPI.recordServiceDetokenize.mockResolvedValueOnce(new Error("Invalid")); try { const response = await vaultController.detokenize(mockRequest, mockOptions); @@ -468,7 +569,7 @@ describe('VaultController detokenize method', () => { mockVaultClient.tokensAPI.recordServiceDetokenize.mockRejectedValueOnce(unexpectedError); await expect(vaultController.detokenize(mockRequest, mockOptions)).rejects.toThrow('Validation error'); - expect(mockVaultClient.tokensAPI.recordServiceDetokenize).toHaveBeenCalled(); + expect(mockVaultClient.tokensAPI.recordServiceDetokenize).not.toHaveBeenCalled(); }); }); @@ -485,7 +586,7 @@ describe('VaultController delete method', () => { initAPI: jest.fn(), getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}) + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})) }; vaultController = new VaultController(mockVaultClient); jest.clearAllMocks(); @@ -525,7 +626,7 @@ describe('VaultController delete method', () => { await expect(vaultController.delete(mockRequest)).rejects.toThrow('Validation error'); expect(validateDeleteRequest).toHaveBeenCalled(); - expect(mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord).not.toHaveBeenCalled(); }); test('should handle API errors during delete', async () => { @@ -533,9 +634,11 @@ describe('VaultController delete method', () => { deleteIds: ['id123'], tableName: 'testTable', }; - const errorResponse = new Error('Validation error'); - - mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord.mockRejectedValueOnce(errorResponse.message); + const errorResponse = new Error('Invalid'); + validateDeleteRequest.mockImplementation(() => { + // throw new Error('Validation error'); + }); + mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord.mockRejectedValueOnce(errorResponse); await expect(vaultController.delete(mockRequest)).rejects.toEqual(errorResponse); expect(mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord).toHaveBeenCalled(); @@ -570,7 +673,7 @@ describe('VaultController delete method', () => { mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord.mockRejectedValueOnce(errorResponse); await expect(vaultController.delete(mockRequest)).rejects.toEqual(errorResponse); - expect(mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord).not.toHaveBeenCalled(); }); }); @@ -685,7 +788,7 @@ describe('VaultController query method', () => { initAPI: jest.fn(), getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}) + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})) }; vaultController = new VaultController(mockVaultClient); jest.clearAllMocks(); @@ -716,6 +819,26 @@ describe('VaultController query method', () => { expect(response.errors).toHaveLength(0); }); + test('should successfully query records as null', async () => { + const mockRequest = { + query: 'SELECT * FROM table WHERE id=1', + }; + const mockResponseData = {data:null}; + + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockResolvedValueOnce(mockResponseData); + + const response = await vaultController.query(mockRequest); + + expect(mockVaultClient.queryAPI.queryServiceExecuteQuery).toHaveBeenCalledWith( + mockVaultClient.vaultId, + expect.any(Object), // Query body + expect.any(Object) // Headers + ); + expect(response).toBeInstanceOf(QueryResponse); + expect(response.fields).toHaveLength(0); + expect(response.errors).toHaveLength(0); + }); + test('should handle validation errors', async () => { const mockRequest = { query: 'SELECT * FROM table WHERE id=1', @@ -727,16 +850,18 @@ describe('VaultController query method', () => { await expect(vaultController.query(mockRequest)).rejects.toThrow('Validation error'); expect(validateQueryRequest).toHaveBeenCalled(); - expect(mockVaultClient.queryAPI.queryServiceExecuteQuery).toHaveBeenCalled(); + expect(mockVaultClient.queryAPI.queryServiceExecuteQuery).not.toHaveBeenCalled(); }); test('should handle API errors during query execution', async () => { const mockRequest = { query: 'SELECT * FROM table WHERE id=1', }; - const errorResponse = new Error('Validation error'); - - mockVaultClient.queryAPI.queryServiceExecuteQuery.mockRejectedValueOnce(errorResponse.message); + const errorResponse = new Error('Invalid'); + validateQueryRequest.mockImplementation(() => { + // throw new Error('Validation error'); + }); + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockRejectedValueOnce(errorResponse); await expect(vaultController.query(mockRequest)).rejects.toEqual(errorResponse); expect(mockVaultClient.queryAPI.queryServiceExecuteQuery).toHaveBeenCalled(); @@ -760,7 +885,7 @@ describe('VaultController query method', () => { const mockRequest = { query: 'SELECT * FROM table WHERE id=1', }; - const errorResponse = new Error('Validation error'); + const errorResponse = new Error('Invalid'); mockVaultClient.queryAPI.queryServiceExecuteQuery.mockRejectedValueOnce(errorResponse); @@ -782,7 +907,7 @@ describe('VaultController update method', () => { initAPI: jest.fn(), getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}) + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})) }; vaultController = new VaultController(mockVaultClient); jest.clearAllMocks(); @@ -817,6 +942,31 @@ describe('VaultController update method', () => { expect(response.errors).toHaveLength(0); }); + test('should successfully update record', async () => { + const mockRequest = { + updateData: { field1: 'value1' }, + tableName: 'testTable', + skyflowId: 'id123', + }; + const mockOptions = null; + const mockResponseData = {data: { skyflow_id: 'id123', tokens: { field1: 'token123' } }}; + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockResolvedValueOnce(mockResponseData); + + const response = await vaultController.update(mockRequest, mockOptions); + + expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).toHaveBeenCalledWith( + mockVaultClient.vaultId, + mockRequest.tableName, + mockRequest.skyflowId, + expect.any(Object), // Update data + expect.any(Object) // Headers + ); + expect(response).toBeInstanceOf(UpdateResponse); + expect(response.updatedField.skyflowID).toBe('id123'); + expect(response.updatedField.field1).toBe('token123'); + expect(response.errors).toHaveLength(0); + }); test('should successfully update record using enable tokens', async () => { const mockRequest = { updateData: { field1: 'value1' }, @@ -863,7 +1013,7 @@ describe('VaultController update method', () => { await expect(vaultController.update(mockRequest, mockOptions)).rejects.toThrow('Validation error'); expect(validateUpdateRequest).toHaveBeenCalled(); - expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).not.toHaveBeenCalled(); }); test('should handle API errors during record update', async () => { @@ -876,9 +1026,12 @@ describe('VaultController update method', () => { getReturnTokens: jest.fn().mockReturnValue(true), getTokenMode: jest.fn().mockReturnValue("ENABLE"), }; - const errorResponse = new Error('Validation error'); + validateUpdateRequest.mockImplementation(() => { + // throw new Error('Validation error'); + }); + const errorResponse = new Error('Invalid'); - mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockRejectedValueOnce(errorResponse.message); + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockRejectedValueOnce(errorResponse); await expect(vaultController.update(mockRequest, mockOptions)).rejects.toEqual(errorResponse); expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).toHaveBeenCalled(); @@ -915,7 +1068,10 @@ describe('VaultController update method', () => { getReturnTokens: jest.fn().mockReturnValue(true), getTokenMode: jest.fn().mockReturnValue(false), }; - const errorResponse = new Error('Validation error'); + validateUpdateRequest.mockImplementation(() => { + // throw new Error('Validation error'); + }); + const errorResponse = new Error('Invalid'); mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockRejectedValueOnce(errorResponse); @@ -939,7 +1095,7 @@ describe('VaultController uploadFile method', () => { initAPI: jest.fn(), getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}) + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})) }; mockFormData = require('form-data'); mockFs = require('fs'); @@ -983,7 +1139,7 @@ describe('VaultController uploadFile method', () => { await expect(vaultController.uploadFile(mockRequest)).rejects.toThrow('Validation error'); expect(validateUploadFileRequest).toHaveBeenCalled(); - expect(mockVaultClient.vaultAPI.fileServiceUploadFile).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.fileServiceUploadFile).not.toHaveBeenCalled(); }); test('should handle file stream creation failure', async () => { @@ -999,7 +1155,7 @@ describe('VaultController uploadFile method', () => { }); await expect(vaultController.uploadFile(mockRequest)).rejects.toThrow('Validation error'); - expect(mockFs.createReadStream).toHaveBeenCalledWith(mockRequest.filePath); + expect(mockFs.createReadStream).not.toHaveBeenCalledWith(mockRequest.filePath); expect(mockVaultClient.vaultAPI.fileServiceUploadFile).not.toHaveBeenCalled(); }); @@ -1012,12 +1168,14 @@ describe('VaultController uploadFile method', () => { }; const mockStream = { on: jest.fn() }; jest.spyOn(mockFs, 'createReadStream').mockReturnValueOnce(mockStream); - + validateUploadFileRequest.mockImplementation(() => { + // throw new Error('Validation error'); + }); const errorResponse = new Error('Validation error'); mockVaultClient.vaultAPI.fileServiceUploadFile.mockRejectedValueOnce(errorResponse); await expect(vaultController.uploadFile(mockRequest)).rejects.toEqual(errorResponse); - expect(mockVaultClient.vaultAPI.fileServiceUploadFile).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.fileServiceUploadFile).not.toHaveBeenCalled(); }); test('should log and reject errors during file upload', async () => { @@ -1030,7 +1188,7 @@ describe('VaultController uploadFile method', () => { const mockStream = { on: jest.fn() }; jest.spyOn(mockFs, 'createReadStream').mockReturnValueOnce(mockStream); - const errorResponse = new Error('Validation error'); + const errorResponse = new Error('Invalid'); mockVaultClient.vaultAPI.fileServiceUploadFile.mockRejectedValueOnce(errorResponse); await expect(vaultController.uploadFile(mockRequest)).rejects.toEqual(errorResponse); @@ -1052,7 +1210,7 @@ describe('VaultController get method', () => { initAPI: jest.fn(), getCredentials: jest.fn().mockReturnValue({}), vaultId: 'vault123', - failureResponse: jest.fn().mockRejectedValueOnce({}) + failureResponse: jest.fn().mockRejectedValueOnce(new SkyflowError({http_code:500,message:"Invalid"})) }; vaultController = new VaultController(mockVaultClient); jest.clearAllMocks(); @@ -1078,6 +1236,32 @@ describe('VaultController get method', () => { expect(response.errors).toHaveLength(0); }); + test('should successfully get records for GetRequest with options', async () => { + const mockRequest = createGetRequest(['id1', 'id2']); + const mockResponseData = { data: { records: [{ fields: { field1: 'value1' } }, { fields: { field2: 'value2' } }] } }; + const mockOptions = { + getRedactionType: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(true), + getFields: jest.fn().mockReturnValue(true), + getOffset: jest.fn().mockReturnValue(true), + getLimit: jest.fn().mockReturnValue(true), + getDownloadURL: jest.fn().mockReturnValue(true), + getOrderBy: jest.fn().mockReturnValue(true) + }; + + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockResolvedValueOnce(mockResponseData); + + const response = await vaultController.get(mockRequest,mockOptions); + + // Validate that the correct validation method was called + expect(validateGetRequest).toHaveBeenCalledWith(mockRequest); + + // Validate the response structure and content + expect(response).toBeInstanceOf(GetResponse); + expect(response.data).toEqual([{ field1: 'value1' }, { field2: 'value2' }]); + expect(response.errors).toHaveLength(0); + }); + test('should successfully get records for GetColumnRequest', async () => { const mockRequest = createGetColumnRequest('columnName', ['value1', 'value2']); const mockResponseData = { data: { records:[{ fields: { field1: 'value1' } }]}}; @@ -1105,7 +1289,7 @@ describe('VaultController get method', () => { expect(validateGetRequest).toHaveBeenCalledWith(mockRequest); // Ensure that the API call was not made - expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).not.toHaveBeenCalled(); }); test('should handle validation errors for GetColumnRequest', async () => { @@ -1118,7 +1302,7 @@ describe('VaultController get method', () => { expect(validateGetColumnRequest).toHaveBeenCalledWith(mockRequest); // Ensure that the API call was not made - expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).not.toHaveBeenCalled(); }); test('should handle API errors during get', async () => { @@ -1130,7 +1314,7 @@ describe('VaultController get method', () => { await expect(vaultController.get(mockRequest)).rejects.toEqual(errorResponse); // Validate that the API call was made - expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).toHaveBeenCalled(); + expect(mockVaultClient.vaultAPI.recordServiceBulkGetRecord).not.toHaveBeenCalled(); }); test('should log and reject errors during get', async () => { diff --git a/test/vault/utils/jwt-utils/jwt.test.js b/test/vault/utils/jwt-utils/jwt.test.js index 189995a..2fff9c9 100644 --- a/test/vault/utils/jwt-utils/jwt.test.js +++ b/test/vault/utils/jwt-utils/jwt.test.js @@ -1,15 +1,13 @@ -import { jwtDecode } from 'jwt-decode'; +import jwt_decode from 'jwt-decode'; import { isTokenValid } from '../../../../src/utils/jwt-utils'; -jest.mock('jwt-decode', () => ({ - jwtDecode: jest.fn(), -})); +jest.mock('jwt-decode'); describe('isTokenValid Tests', () => { const mockDecodedPayload = { sub: '12345', name: 'John Doe', exp: 1609459200 }; beforeEach(() => { - jwtDecode.mockReturnValue(mockDecodedPayload); + jwt_decode.mockReturnValue(mockDecodedPayload); }); test('should return false for an invalid token', () => { diff --git a/test/vault/utils/utils.test.js b/test/vault/utils/utils.test.js index 395c7d9..417c318 100644 --- a/test/vault/utils/utils.test.js +++ b/test/vault/utils/utils.test.js @@ -1,13 +1,11 @@ import errorMessages from "../../../src/error/messages"; import { Env, getConnectionBaseURL, getVaultURL, validateToken, isValidURL, fillUrlWithPathAndQueryParams, generateSDKMetrics, printLog, getToken, getBearerToken, MessageType, LogLevel } from "../../../src/utils"; -import { jwtDecode } from 'jwt-decode'; +import jwt_decode from 'jwt-decode'; import os from 'os'; import { generateBearerTokenFromCreds, generateBearerToken } from '../../../src/service-account'; import sdkDetails from '../../../package.json'; -jest.mock('jwt-decode', () => ({ - jwtDecode: jest.fn(), -})); +jest.mock('jwt-decode'); jest.mock('../../../src/service-account', () => ({ generateBearerTokenFromCreds: jest.fn(), @@ -71,7 +69,7 @@ describe('Validate Token Helper', () => { beforeEach(() => { jest.clearAllMocks(); - jwtDecode.mockReturnValue(mockPrevDecodedPayload); + jwt_decode.mockReturnValue(mockPrevDecodedPayload); }); test('should throw an error for invalid token', () => { @@ -80,13 +78,13 @@ describe('Validate Token Helper', () => { }); test('should throw an error for invalid token', () => { - jwtDecode.mockReturnValue(mockFutureDecodedPayload); + jwt_decode.mockReturnValue(mockFutureDecodedPayload); expect(validateToken("connectionId")) .toBeTruthy(); }); test('should throw an error for invalid token', () => { - jwtDecode.mockReturnValue(mockDecodedPayload); + jwt_decode.mockReturnValue(mockDecodedPayload); expect(validateToken("connectionId")) .toBeTruthy(); });