diff --git a/CHANGELOG.md b/CHANGELOG.md index fd91006..8ba980f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [1.12.0] - 2023-10-20 +### Added +- `tokens` option in `get` interface. +- `encodeURI` option in `get` interface. + +## [1.11.0] - 2023-07-19 +### Added +- Added new `delete` interface. ## [1.10.0] - 2023-06-08 ### Added - `redaction` in detokenize interface. diff --git a/README.md b/README.md index a7f460a..e8f7025 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,18 @@ skyflow-node is the Node.js version of Skyflow SDK for the JavaScript programmin - [Requirements](#requirements) - [Configuration](#configuration) - [Usage](#usage) - - [Service Account Bearer Token Generation](#Service-Account-Bearer-Token-Generation) - - [Service Account Bearer Token Generation with Additional Context](#Service-Account-Bearer-Token-Generation-with-Additional-Context) - - [Service Account Scoped Bearer Token Generation](#Service-Account-Scoped-Bearer-Token-Generation) - - [Skyflow Signed Data Tokens Generation](#Skyflow-Signed-Data-Tokens-Generation) + - [Importing `skyflow-node`](#importing-skyflow-node) + - [Service Account Bearer Token Generation](#service-account-bearer-token-generation) + - [Service Account Bearer Token Generation with Additional Context](#service-account-bearer-token-generation-with-additional-context) + - [Service Account Scoped Bearer Token Generation](#service-account-scoped-bearer-token-generation) + - [Skyflow Signed Data Tokens Generation](#skyflow-signed-data-tokens-generation) - [Vault APIs](#vault-apis) - [Insert](#insert) - [Detokenize](#detokenize) - [Get By Id](#get-by-id) - [Get](#get) - [Update](#update) + - [Delete](#delete) - [Invoke Connection](#invoke-connection) - [Logging](#logging) - [Reporting a Vulnerability](#reporting-a-vulnerability) @@ -112,7 +114,7 @@ let bearerToken = ''; function getSkyflowBearerToken() { return new Promise(async (resolve, reject) => { try { - if (isExpired(bearerToken)) resolve(bearerToken); + if (!isExpired(bearerToken)) resolve(bearerToken); else { let response = await generateBearerTokenFromCreds( JSON.stringify(credentials) @@ -195,7 +197,7 @@ function getSkyflowBearerToken() { const options = { ctx: 'CONTEXT_ID', } - if (isExpired(bearerToken)) resolve(bearerToken); + if (!isExpired(bearerToken)) resolve(bearerToken); else { let response = await generateBearerTokenFromCreds( JSON.stringify(credentials), @@ -279,7 +281,7 @@ function getSkyflowBearerToken() { const options = { roleIDs: ['ROLE_ID1', 'ROLE_ID2'], }; - if (isExpired(bearerToken)) resolve(bearerToken); + if (!isExpired(bearerToken)) resolve(bearerToken); else { let response = await generateBearerTokenFromCreds( JSON.stringify(credentials), @@ -909,6 +911,65 @@ Response: ] } ``` +#### Delete + +To delete data from the vault, use the `delete(records, options?)` method of the Skyflow client. The `records` parameter takes an array of records to delete in the following format. The `options` parameter is optional and takes an object of deletion parameters. Currently, there are no supported deletion parameters. + +Call schema: +```js +const deleteInput = { + records: [ + { + id: "", // skyflow id of the record to delete + table: "" // Table from which the record is to be deleted + }, + { + // ...additional records here + }, + ] +}; + +const options = { + // Optional +} +``` + +[Example](https://github.com/skyflowapi/skyflow-node/blob/master/samples/vault-api/Delete.ts) to delete by ID using `skyflow_ids` + +```js +const deleteInput = { + records: [ + { + id: "29ebda8d-5272-4063-af58-15cc674e332b", + table: "cards", + }, + { + id: "d5f4b926-7b1a-41df-8fac-7950d2cbd923", + table: "cards", + } + ], +}; + +const options = {}; + +const response = skyflowClient.delete(deleteInput, options); +console.log(response); +``` +Response: +```json +{ + "records": [ + { + "skyflow_id": "29ebda8d-5272-4063-af58-15cc674e332b", + "deleted": true, + }, + { + "skyflow_id": "29ebda8d-5272-4063-af58-15cc674e332b", + "deleted": true, + } + ] +} +``` #### Invoke Connection Using the InvokeConnection method, you can integrate their server-side application with third party APIs and services without directly handling sensitive data. Prior to invoking the InvokeConnection method, you must have created a connection and have a connectionURL already generated. Once you have the connectionURL, you can invoke a connection by using the `invokeConnection(config)` method. The config object must include a connectionURL and methodName. The other fields are optional. diff --git a/package.json b/package.json index cd1ce19..8a02637 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "skyflow-node", - "version": "1.10.0", + "version": "1.12.0", "description": "Skyflow SDK for Node.js", "main": "./lib/index.js", "module": "./lib/index.js", @@ -48,7 +48,7 @@ }, "devDependencies": { "@babel/cli": "^7.0.0", - "@babel/core": "^7.9.6", + "@babel/core": "^7.23.2", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-decorators": "^7.10.1", "@babel/plugin-proposal-object-rest-spread": "^7.10.1", @@ -66,7 +66,6 @@ "@types/qs": "^6.9.7", "babel-loader": "^8.0.0", "chai": "^4.1.2", - "coveralls": "^3.0.1", "crypto-browserify": "^3.12.0", "eslint": "^8.12.0", "eslint-plugin-sonarjs": "^0.13.0", @@ -74,9 +73,9 @@ "husky": "^1.3.1", "istanbul": "^0.4.5", "jest": "^27.3.1", - "jsdoc": "^3.6.3", + "jsdoc": "^4.0.2", "json-loader": "~0.5.4", - "nodemon": "^2.0.14", + "nodemon": "^3.0.1", "null-loader": "^0.1.1", "nyc": "^15.1.0", "prettier": "^1.13.7", diff --git a/samples/package.json b/samples/package.json index caf3676..49f65a0 100644 --- a/samples/package.json +++ b/samples/package.json @@ -9,6 +9,6 @@ "author": "", "license": "ISC", "dependencies": { - "skyflow-node": "^1.9.0" + "skyflow-node": "^1.11.0" } } diff --git a/samples/vault-api/Delete.ts b/samples/vault-api/Delete.ts new file mode 100644 index 0000000..2d6068f --- /dev/null +++ b/samples/vault-api/Delete.ts @@ -0,0 +1,58 @@ +/* + Copyright (c) 2023 Skyflow, Inc. +*/ +import { + Skyflow, + generateBearerToken, + isExpired, + setLogLevel, + LogLevel, +} from "skyflow-node"; + +const filePath = ""; +setLogLevel(LogLevel.INFO); +let bearerToken = ""; + +const skyflow = Skyflow.init({ + vaultID: "", + vaultURL: "", + getBearerToken: () => { + return new Promise((resolve, reject) => { + if (!isExpired(bearerToken)) { + resolve(bearerToken); + } else { + generateBearerToken(filePath) + .then((response) => { + bearerToken = response.accessToken; + resolve(bearerToken); + }) + .catch((err) => { + reject(err); + }); + } + }); + }, +}); + +const result = skyflow.delete({ + records: [ + { + id: "", + table: "", + table: " { + console.log("delete result:"); + console.log(JSON.stringify(response)); + }) + .catch((error) => { + console.log("delete error: "); + console.log(JSON.stringify(error)); + }); diff --git a/src/vault-api/Controller.ts b/src/vault-api/Controller.ts index 650d8ea..41668a9 100644 --- a/src/vault-api/Controller.ts +++ b/src/vault-api/Controller.ts @@ -4,12 +4,15 @@ import Client from './client'; import { - validateConnectionConfig, validateInsertRecords, validateDetokenizeInput, validateGetByIdInput, validateInitConfig, validateUpsertOptions, validateUpdateInput, validateGetInput, + validateConnectionConfig, validateInsertRecords, validateDetokenizeInput, validateGetByIdInput, validateInitConfig, validateUpsertOptions, validateUpdateInput, validateGetInput, validateDeleteInputAndOptions } from './utils/validators'; import { ContentType, + IDeleteInput, + IDeleteOptions, IGetInput, + IGetOptions, IInsertOptions, IUpdateInput, IUpdateOptions, @@ -40,6 +43,7 @@ import { import { isTokenValid } from './utils/jwt-utils'; import SKYFLOW_ERROR_CODE from './utils/constants'; import SkyflowError from './libs/SkyflowError'; +import { deleteRecordsBySkyflowID } from './core/Delete'; class Controller { #client: Client; @@ -148,12 +152,12 @@ class Controller { }); } - get(getInput: IGetInput) { + get(getInput: IGetInput,options:IGetOptions={}) { return new Promise((resolve, reject) => { try { validateInitConfig(this.#client.config) printLog(logs.infoLogs.VALIDATE_GET_INPUT, MessageType.LOG); - validateGetInput(getInput); + validateGetInput(getInput,options); printLog(parameterizedString(logs.infoLogs.EMIT_REQUEST, TYPES.GET), MessageType.LOG); @@ -161,7 +165,8 @@ class Controller { fetchRecordsBySkyflowID( getInput.records, this.#client, - res + res, + options ).then( (resolvedResult) => { printLog(logs.infoLogs.GET_BY_SKYFLOWID_RESOLVED, MessageType.LOG); @@ -243,6 +248,32 @@ class Controller { }); } + delete(deleteInput: IDeleteInput, options?: IDeleteOptions) { + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#client.config); + validateDeleteInputAndOptions(deleteInput, options); + this.getToken().then((authToken) => { + deleteRecordsBySkyflowID(deleteInput.records, options = {}, this.#client, authToken) + .then((response) => { + printLog(logs.infoLogs.DELETE_REQUEST_RESOLVED, MessageType.LOG); + resolve(response); + }).catch((error) => { + printLog(logs.errorLogs.DELETE_REQUEST_REJECTED, MessageType.ERROR); + reject(error); + }); + }).catch((err) => { + reject(err); + }) + + } catch (error) { + if (error instanceof Error) + printLog(error.message, MessageType.ERROR); + reject(error); + } + }) + } + insertData(records, options) { const requestBody = constructInsertRecordRequest(records, options); return new Promise((rootResolve, rootReject) => { diff --git a/src/vault-api/Skyflow.ts b/src/vault-api/Skyflow.ts index 4587276..8743d20 100644 --- a/src/vault-api/Skyflow.ts +++ b/src/vault-api/Skyflow.ts @@ -6,25 +6,28 @@ * @module Skyflow */ - import Client from './client'; - import { printLog } from './utils/logs-helper'; - import logs from './utils/logs'; - import Controller from './Controller'; - import { - IRevealResponseType, - IConnectionConfig, - RequestMethod, - IInsertRecordInput, - IDetokenizeInput, - IGetByIdInput, - RedactionType, - MessageType, - IInsertOptions, - IUpdateInput, - IUpdateOptions, - IGetInput, - } from './utils/common'; - import { formatVaultURL } from './utils/helpers'; +import Client from './client'; +import { printLog } from './utils/logs-helper'; +import logs from './utils/logs'; +import Controller from './Controller'; +import { + IRevealResponseType, + IConnectionConfig, + RequestMethod, + IInsertRecordInput, + IDetokenizeInput, + IGetByIdInput, + RedactionType, + MessageType, + IInsertOptions, + IUpdateInput, + IUpdateOptions, + IGetInput, + IGetOptions, + IDeleteInput, + IDeleteOptions, +} from './utils/common'; +import { formatVaultURL } from './utils/helpers'; /** @@ -171,6 +174,11 @@ return this.#Controller.update(updateInput,options); } + delete(deleteInput: IDeleteInput, options?: IDeleteOptions) { + printLog(logs.infoLogs.UPDATE_TRIGGERED, MessageType.LOG); + return this.#Controller.delete(deleteInput, options) + } + static get RedactionType() { return RedactionType; } @@ -180,4 +188,4 @@ } } export default Skyflow; - \ No newline at end of file + diff --git a/src/vault-api/core/Delete.ts b/src/vault-api/core/Delete.ts new file mode 100644 index 0000000..922074e --- /dev/null +++ b/src/vault-api/core/Delete.ts @@ -0,0 +1,83 @@ +import Client from "../client"; +import SkyflowError from "../libs/SkyflowError"; +import { IDeleteOptions, IDeleteRecord } from "../utils/common"; + +const formatForPureJsFailure = (cause, skyflowId: string) => ({ + id: skyflowId, + ...new SkyflowError({ + code: cause?.error?.code, + description: cause?.error?.description, + }, [], true), +}); + +const deleteRecordInVault = ( + deleteRecord: IDeleteRecord, + options: IDeleteOptions, + client: Client, + authToken: string, +): Promise => { + const vaultEndPointURL: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${deleteRecord.table}/${deleteRecord.id}`; + return client.request({ + requestMethod: 'DELETE', + url: vaultEndPointURL, + headers: { + Authorization: `Bearer ${authToken}`, + 'Content-Type': 'application/json', + } + }); +}; + +/** Delete By Skyflow ID */ +export const deleteRecordsBySkyflowID = ( + deleteRecords: IDeleteRecord[], + options: IDeleteOptions, + client: Client, + authToken: String +) => { + return new Promise((rootResolve, rootReject) => { + const vaultResponseSet: Promise[] = deleteRecords.map( + (deleteRecord) => + new Promise((resolve) => { + const deleteResponse: any = []; + deleteRecordInVault( + deleteRecord, + options, + client, + authToken as string + ) + .then( + (response: any) => { + deleteResponse.push(response); + }, + (cause: any) => { + deleteResponse.push(formatForPureJsFailure(cause, deleteRecord.id)); + } + ) + .finally(() => { + resolve(deleteResponse); + }); + }) + ); + + Promise.allSettled(vaultResponseSet).then((resultSet) => { + const recordsResponse: Record[] = []; + const errorResponse: Record[] = []; + resultSet.forEach((result) => { + if (result.status === "fulfilled") { + result.value.forEach((res: Record) => { + if (Object.prototype.hasOwnProperty.call(res, "error")) { + errorResponse.push(res); + } else { + recordsResponse.push(res); + } + }); + } + }); + if (errorResponse.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse.length === 0) { + rootReject({ errors: errorResponse }); + }else rootReject({ records: recordsResponse, errors: errorResponse }); + }); + }); +}; diff --git a/src/vault-api/core/Reveal.ts b/src/vault-api/core/Reveal.ts index 86a40b2..32a7a5c 100644 --- a/src/vault-api/core/Reveal.ts +++ b/src/vault-api/core/Reveal.ts @@ -4,7 +4,7 @@ import Client from '../client'; import SkyflowError from '../libs/SkyflowError'; import { - ISkyflowIdRecord, IRevealRecord, IRevealResponseType, IUpdateRecord, IUpdateOptions,RedactionType + ISkyflowIdRecord, IRevealRecord, IRevealResponseType, IUpdateRecord, IUpdateOptions,RedactionType, IGetOptions } from '../utils/common'; import 'core-js/modules/es.promise.all-settled'; interface IApiSuccessResponse { @@ -56,6 +56,7 @@ const getSkyflowIdRecordsFromVault = ( skyflowIdRecord: ISkyflowIdRecord, client: Client, authToken: string, + options?: IGetOptions ) => { let paramList: string = ''; @@ -64,11 +65,26 @@ const getSkyflowIdRecordsFromVault = ( paramList += `skyflow_ids=${skyflowId}&`; }); - skyflowIdRecord.columnValues?.forEach((column) => { - paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${column}&`; - }) + if (options && Object.prototype.hasOwnProperty.call(options, 'encodeURI') && options?.encodeURI === false){ + skyflowIdRecord.columnValues?.forEach((column) => { + paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${column}&`; + }); + } else { + skyflowIdRecord.columnValues?.forEach((column) => { + var encode_column_value = encodeURIComponent(column) + paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${encode_column_value}&`; + }); + } - const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}?${paramList}redaction=${skyflowIdRecord.redaction}`; + if(options && Object.prototype.hasOwnProperty.call(options,'tokens')){ + paramList += `tokenization=${options.tokens}&` + } + + if(skyflowIdRecord?.redaction){ + paramList += `redaction=${skyflowIdRecord.redaction}` + } + + const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}?${paramList}`; return client.request({ requestMethod: 'GET', @@ -158,12 +174,13 @@ export const fetchRecordsByTokenId = ( export const fetchRecordsBySkyflowID = async ( skyflowIdRecords: ISkyflowIdRecord[], client: Client, - authToken: string + authToken: string, + options?: IGetOptions ) => new Promise((rootResolve, rootReject) => { let vaultResponseSet: Promise[]; vaultResponseSet = skyflowIdRecords.map( (skyflowIdRecord) => new Promise((resolve, reject) => { - getSkyflowIdRecordsFromVault(skyflowIdRecord, client, authToken as string) + getSkyflowIdRecordsFromVault(skyflowIdRecord, client, authToken as string,options) .then( (resolvedResult: any) => { const response: any[] = []; diff --git a/src/vault-api/utils/common/index.ts b/src/vault-api/utils/common/index.ts index fb2559b..8777f91 100644 --- a/src/vault-api/utils/common/index.ts +++ b/src/vault-api/utils/common/index.ts @@ -103,7 +103,7 @@ export interface IDetokenizeInput { */ export interface ISkyflowIdRecord { ids?: string[]; - redaction: RedactionType; + redaction?: RedactionType; table: string; columnName?: string; columnValues?: string[]; @@ -226,4 +226,21 @@ export interface IUpdateOptions{ tokens: boolean } -export const SDK_METRICS_HEADER_KEY = "sky-metadata"; \ No newline at end of file +export interface IGetOptions{ + tokens?: boolean + encodeURI?: boolean +} +export interface IDeleteRecord { + id: string; + table: string; +} + +export interface IDeleteInput { + records: IDeleteRecord[]; +} + +export interface IDeleteOptions { + +} + +export const SDK_METRICS_HEADER_KEY = "sky-metadata"; diff --git a/src/vault-api/utils/constants.ts b/src/vault-api/utils/constants.ts index 3283cd0..d6f3a13 100644 --- a/src/vault-api/utils/constants.ts +++ b/src/vault-api/utils/constants.ts @@ -127,11 +127,50 @@ const SKYFLOW_ERROR_CODE = { code: 400, description: logs.errorLogs.INVALID_GET_BY_ID_INPUT, }, + INVALID_TOKENS_IN_GET: { + code: 400, + description: logs.errorLogs.INVALID_TOKENS_IN_GET, + }, + TOKENS_GET_COLUMN_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.TOKENS_GET_COLUMN_NOT_SUPPORTED, + }, + REDACTION_WITH_TOKENS_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.REDACTION_WITH_TOKENS_NOT_SUPPORTED, + }, + INVALID_ENCODE_URI_IN_GET: { + code: 400, + description: logs.errorLogs.INVALID_ENCODE_URI_IN_GET, + }, + MISSING_ID_IN_DELETE: { + code: 400, + description: logs.errorLogs.MISSING_ID_IN_DELETE, + }, + INVALID_ID_IN_DELETE: { + code: 400, + description: logs.errorLogs.INVALID_ID_IN_DELETE, + }, + MISSING_TABLE_IN_DELETE: { + code: 400, + description: logs.errorLogs.MISSING_TABLE_IN_DELETE, + }, + INVALID_TABLE_IN_DELETE: { + code: 400, + description: logs.errorLogs.INVALID_TABLE_IN_DELETE, + }, + INVALID_DELETE_INPUT: { + code: 400, + description: logs.errorLogs.INVALID_DELETE_INPUT, + }, + INVLAID_DELETE_RECORDS_INPUT: { + code: 400, + description: logs.errorLogs.INVLAID_DELETE_RECORDS_INPUT, + }, DETOKENIZE_INVALID_REDACTION_TYPE:{ code: 400, description: logs.errorLogs.DETOKENIZE_INVALID_REDACTION_TYPE, } - }; export default SKYFLOW_ERROR_CODE; diff --git a/src/vault-api/utils/logs.ts b/src/vault-api/utils/logs.ts index f6c9783..81f16a5 100644 --- a/src/vault-api/utils/logs.ts +++ b/src/vault-api/utils/logs.ts @@ -27,6 +27,8 @@ const logs = { GET_BY_ID_TRIGGERED: 'Get by ID triggered.', GET_CALL_TRIGGERED: 'Get call triggered.', INVOKE_CONNECTION_TRIGGERED: 'Invoke connection triggered.', + DELETE_TRIGGERED: 'Delete method Triggered', + DELETE_REQUEST_RESOLVED: 'Delete method is resolved', EMIT_REQUEST: 'Emitted %s1 request.', FETCH_RECORDS_RESOLVED: 'Detokenize request is resolved.', INSERT_RECORDS_RESOLVED: 'Insert request is resolved.', @@ -47,6 +49,7 @@ const logs = { INVALID_VAULTURL_IN_INIT: 'Interface: init - Invalid client credentials. Expecting https://XYZ for vaultURL', GET_BEARER_TOKEN_IS_REQUIRED: 'Interface: init - Invalid client credentials. getBearerToken is required.', BEARER_TOKEN_REJECTED: 'Interface: init - GetBearerToken promise got rejected.', + INVALID_ENCODE_URI_IN_GET: 'Interface: get method - Invalid encodeURI type in get.', INVALID_BEARER_TOKEN: 'Bearer token is invalid or expired.', INVALID_VAULT_ID: 'Vault Id is invalid or cannot be found.', EMPTY_VAULT_ID: 'VaultID is empty.', @@ -126,6 +129,16 @@ const logs = { INVALID_UPDATE_INPUT: 'Interface: update method - Invalid argument , object with records key is required.', INVALID_RECORDS_UPDATE_INPUT: 'Interface: update method - Invalid records type, records should be an array of objects.', INVALID_GET_BY_ID_INPUT: 'Interface: getById method - columnName or columnValues cannot be passed, use get method instead.', + INVALID_TOKENS_IN_GET: 'Interface: get method - Invalid tokens in options. tokens of type boolean is required.', + TOKENS_GET_COLUMN_NOT_SUPPORTED: 'Interface: get method - column_name or column_values cannot be used with tokens in options.', + REDACTION_WITH_TOKENS_NOT_SUPPORTED: 'Interface: get method - redaction cannot be used when tokens are true in options.', + INVALID_DELETE_INPUT: 'Interface: delete method - Invalid argument , object with records key is required.', + INVLAID_DELETE_RECORDS_INPUT: 'Interface: delete method - Invalid records type, records should be an array of objects.', + MISSING_ID_IN_DELETE: 'Interface: delete method - id key is required in records object at index %s1', + INVALID_ID_IN_DELETE: 'Interface: delete method - Invalid id in records object at index %s1, id of type non empty string is required.', + MISSING_TABLE_IN_DELETE: 'Interface: delete method - table key is required in records object at index %s1', + INVALID_TABLE_IN_DELETE: 'Interface: delete method - Invalid table in records object at index %s1, table of type non empty string is required.', + DELETE_REQUEST_REJECTED: 'Delete request is rejected.', DETOKENIZE_INVALID_REDACTION_TYPE:'Interface: detokenize method - Invalid redaction type, use Skyflow.RedactionType enum.', }, warnLogs: { diff --git a/src/vault-api/utils/validators/index.ts b/src/vault-api/utils/validators/index.ts index 4c06e16..6b85bb6 100644 --- a/src/vault-api/utils/validators/index.ts +++ b/src/vault-api/utils/validators/index.ts @@ -4,7 +4,7 @@ import SkyflowError from '../../libs/SkyflowError'; import { ISkyflow } from '../../Skyflow'; import { - IInsertRecordInput, IDetokenizeInput, RedactionType, IGetByIdInput, IConnectionConfig, RequestMethod, IUpdateInput, IGetInput, + IInsertRecordInput, IDetokenizeInput, RedactionType, IGetByIdInput, IConnectionConfig, RequestMethod, IUpdateInput, IGetInput, IGetOptions, IDeleteInput, IDeleteOptions, } from '../common'; import SKYFLOW_ERROR_CODE from '../constants'; @@ -93,7 +93,7 @@ export const validateGetByIdInput = (getByIdInput: IGetByIdInput) => { }); }; -export const validateGetInput = (getInput: IGetInput) => { +export const validateGetInput = (getInput: IGetInput,options?:IGetOptions) => { if (!Object.prototype.hasOwnProperty.call(getInput, 'records')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_RECORDS); } @@ -102,6 +102,14 @@ export const validateGetInput = (getInput: IGetInput) => { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); } + if (Object.prototype.hasOwnProperty.call(options, 'tokens') && (typeof options?.tokens !== 'boolean')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET); + } + + if (Object.prototype.hasOwnProperty.call(options, 'encodeURI') && (typeof options?.encodeURI !== 'boolean')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET); + } + records.forEach((record) => { if (Object.keys(record).length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); @@ -114,11 +122,14 @@ export const validateGetInput = (getInput: IGetInput) => { }); const recordRedaction = record.redaction; - if (!recordRedaction) throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_REDACTION); - if (!Object.values(RedactionType).includes(recordRedaction)) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REDACTION_TYPE); + if(!(options && Object.prototype.hasOwnProperty.call(options, 'tokens') && options?.tokens === true)){ + if (!recordRedaction) throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_REDACTION); + if (!Object.values(RedactionType).includes(recordRedaction)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REDACTION_TYPE); + } } + const recordTable = record.table; if (!Object.prototype.hasOwnProperty.call(record, 'table')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TABLE); } @@ -143,6 +154,13 @@ export const validateGetInput = (getInput: IGetInput) => { if (eachColumnValue === '') throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_COLUMN_VALUE); }); } + if(options && Object.prototype.hasOwnProperty.call(options, 'tokens') && options?.tokens === true){ + if(columnName || columnValues) + throw new SkyflowError(SKYFLOW_ERROR_CODE.TOKENS_GET_COLUMN_NOT_SUPPORTED) + + if(recordRedaction) + throw new SkyflowError(SKYFLOW_ERROR_CODE.REDACTION_WITH_TOKENS_NOT_SUPPORTED) + } }); }; @@ -264,4 +282,44 @@ export const validateUpdateInput = (updateInput: IUpdateInput) => { } else { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_UPDATE_INPUT); } -}; \ No newline at end of file +}; + +export const validateDeleteInputAndOptions = (deleteInput: IDeleteInput, options?: IDeleteOptions) => { + if (deleteInput) { + if (!Object.prototype.hasOwnProperty.call(deleteInput, 'records')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_RECORDS); + } + + if (!Array.isArray(deleteInput.records)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVLAID_DELETE_RECORDS_INPUT) + } + const { records } = deleteInput; + if (records.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); + } + records.forEach((deleteRecord, index) => { + if (Object.keys(deleteRecord).length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); + } + + if (!Object.prototype.hasOwnProperty.call(deleteRecord, 'id')){ + throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_ID_IN_DELETE, [index]); + } + + if (typeof deleteRecord.id !== 'string' || deleteRecord.id.trim().length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ID_IN_DELETE, [index]); + } + + if (!Object.prototype.hasOwnProperty.call(deleteRecord, 'table')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TABLE_IN_DELETE, [index]); + } + + const recordTable = deleteRecord.table; + if (typeof recordTable !== 'string' || recordTable.trim().length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TABLE_IN_DELETE, [index]); + } + }); + } else { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DELETE_INPUT); + } +} \ No newline at end of file diff --git a/test/vault-api/Delete.test.js b/test/vault-api/Delete.test.js new file mode 100644 index 0000000..1bd03a9 --- /dev/null +++ b/test/vault-api/Delete.test.js @@ -0,0 +1,187 @@ +import Skyflow from "../../src/vault-api/Skyflow"; +import clientModule from "../../src/vault-api/client"; +import logs from "../../src/vault-api/utils/logs"; + +jest.mock("../../src/vault-api/utils/jwt-utils", () => ({ + __esModule: true, + isTokenValid: jest.fn((token) => token === "token"), +})); + +jest.mock("../../src/vault-api/client"); + +const errorDeleteInput = { + records: [ + { + id: "invalid_delete_id", + table: "table1", + }, + ], +}; + +const errorDeleteRequestResponse = { + error: { + code: "404", + description: "No Records Found.", + }, +}; + +const deleteFailure = { + errors: [ + { + id : 'invalid_delete_id', + ...errorDeleteRequestResponse, + } + ] +}; + +describe("testing delete with invalid bearer token", () => { + test("delete failure with invalid token 1", (done) => { + try { + const skyflowConfig = { + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((_, reject) => { + reject("invalid token"); + }); + }, + }; + + const clientReq = jest.fn(() => + Promise.reject(errorDeleteRequestResponse) + ); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => { + return mockClient; + }); + + const skyflow = Skyflow.init({ + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((_, reject) => { + reject("invalid token"); + }); + }, + }); + + const result = skyflow.delete(errorDeleteInput); + try { + result.catch((err) => { + expect(err).toEqual("invalid token"); + done(); + }); + } catch (err) { + done(err); + } + } catch (err) { + done(err); + } + }); + + test("delete failure with invalid token 2", (done) => { + try { + const skyflowConfig = { + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve(""); + }); + }, + }; + + const clientReq = jest.fn(() => + Promise.reject(errorDeleteRequestResponse) + ); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => { + return mockClient; + }); + + const skyflow = Skyflow.init({ + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve(""); + }); + }, + }); + + const result = skyflow.delete(errorDeleteInput); + try { + result.catch((err) => { + expect(err.message).toEqual(logs.errorLogs.INVALID_BEARER_TOKEN); + done(); + }); + } catch (err) { + done(err); + } + } catch (err) { + done(err); + } + }); + + test("delete failure with invalid token 3", (done) => { + try { + const skyflowConfig = { + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token"); + }); + }, + }; + + const clientReq = jest.fn(() => + Promise.reject(errorDeleteRequestResponse) + ); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => { + return mockClient; + }); + + const skyflow = Skyflow.init({ + vaultID: "", + vaultURL: "https://www.vaulturl.com", + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token"); + }); + }, + }); + + const result = skyflow.delete(errorDeleteInput); + try { + result.catch((err) => { + expect(err).toEqual(deleteFailure); + done(); + }); + } catch (err) { + done(err); + } + } catch (err) { + done(err); + } + }); +}); diff --git a/test/vault-api/Skyflow.test.js b/test/vault-api/Skyflow.test.js index ef9f091..7481b42 100644 --- a/test/vault-api/Skyflow.test.js +++ b/test/vault-api/Skyflow.test.js @@ -524,6 +524,13 @@ const getByIdInput = { }], }; +const getByIdInputWithoutRedaction = { + records: [{ + ids: ['id'], + table: 'cards', + }], +}; + const getByIdInputMissingIds = { records: [{ table: 'cards', @@ -1787,4 +1794,479 @@ describe('skyflow detokenize with redaction', () => { }) }); +}); + +describe('get method with options', () => { + let skyflow; + beforeEach(() => { + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: jest.fn(), + }); + }); + + + test('get method should send request url with tokenization true when tokens are true', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInputWithoutRedaction, { tokens: true }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=true')).toBe(true); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + + test('get method should send request url with tokenization false and redaction when tokens are false', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInput, { tokens: false }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=false')).toBe(true); + expect((reqArg.url).includes('redaction=PLAIN_TEXT')).toBe(true); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + test('get method should not send redaction along with url if redaction is not passed.', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInputWithoutRedaction, { tokens: true }); + response.then((res) => { + console.log(reqArg.url); + expect((reqArg.url).includes('redaction=PLAIN_TEXT')).toBe(false); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + test('get method should throw error when tokens options is invalid type value', (done) => { + + skyflow.get(getByIdInput, { tokens: '12343' }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is invalid type value', (done) => { + + skyflow.get(getByIdInput, { tokens: "true" }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is null', (done) => { + skyflow.get(getByIdInput, { tokens: null }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is undefined', (done) => { + skyflow.get(getByIdInput, { tokens: undefined }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options along with column values', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { tokens: true }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.TOKENS_GET_COLUMN_NOT_SUPPORTED.description); + done(); + }) + }); + + test('get method should throw error when tokens options used along with redaction', (done) => { + skyflow.get(getByIdInput, { tokens: true }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.REDACTION_WITH_TOKENS_NOT_SUPPORTED.description); + done(); + }) + }); + + test('get method should encode column values when encodeURI option is true', (done) => { + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes); + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("encodeURI") + }) + } + }); + + const response = skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: true }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=false')).toBe(false); + done(); + }).catch((er) => { + done(er) + }); + }); + + test('get method should not encode column values when encodeURI option is false', (done) => { + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes); + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => mockClient); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("encodeURI") + }) + } + }); + + const response = skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: false }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=false')).toBe(false); + done(); + }).catch((er) => { + done(er) + }); + }); + + test('get method should throw error when encodeURI options is invalid type value', (done) => { + + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: '12343' }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when encodeURI options is null', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: null }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when encodeURI options is undefined', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: undefined }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); +}); + +const deleteInput = { + records: [ + { + id: 'test_delete_id', + table: 'table1', + } + ] +}; + +const errorDeleteInput = { + records: [ + { + id: 'invalid_delete_id', + table: 'table1', + } + ] +}; + +const partialDeleteSuccessInput = { + records: [ + ...deleteInput.records, + ...errorDeleteInput.records, + ] +}; + +const successDeleteRequestResponse = { + skyflow_id: 'test_delete_id', + deleted: true +}; + +const errorDeleteRequestResponse = { + error: { + code: '404', + description: 'No Records Found.' + } +}; + +const deleteResponse = { + records: [ + { + skyflow_id: 'test_delete_id', + deleted: true + } + ] +}; + +const deleteFailure = { + errors: [ + { + id : 'invalid_delete_id', + ...errorDeleteRequestResponse, + } + ] +}; + +const partialDeleteSuccess = { + ...deleteResponse, + ...deleteFailure, +}; + +describe('skyflow delete method', () => { + test('delete success', (done) => { + try { + jest.mock('../../src/vault-api/utils/jwt-utils', () => ({ + __esModule: true, + isTokenValid: jest.fn(() => true), + })); + + const clientReq = jest.fn(() => Promise.resolve(successDeleteRequestResponse)); + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata:{} + } + + clientModule.mockImplementation(() => { return mockClient }); + const skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const result = skyflow.delete(deleteInput); + result.then((response) => { + expect(response).toEqual(deleteResponse); + done(); + }).catch((err) => { + done(err); + }); + } catch (err) { + done(err); + } + }) + + test('delete failure', (done) => { + try { + jest.mock('../../src/vault-api/utils/jwt-utils', () => ({ + __esModule: true, + isTokenValid: jest.fn(() => true), + })); + + const clientReq = jest.fn(() => Promise.reject(errorDeleteRequestResponse)); + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata:{} + } + + clientModule.mockImplementation(() => { return mockClient }); + const skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const result = skyflow.delete(errorDeleteInput); + try { + result.catch((err) => { + expect(err).toEqual(deleteFailure); + done(); + }); + } catch (err) { + done(err); + } + } catch (err) { + done(err); + } + }) + + test('delete partial success', (done) => { + try { + jest.mock('../../src/vault-api/utils/jwt-utils', () => ({ + __esModule: true, + isTokenValid: jest.fn(() => true), + })); + + const clientReq = jest.fn((args) => { + const check = args.url.includes('test_delete_id') + if (check) { + return Promise.resolve(successDeleteRequestResponse); + } else { + return Promise.reject(errorDeleteRequestResponse); + } + }); + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata:{} + } + + clientModule.mockImplementation(() => { return mockClient }); + const skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const result = skyflow.delete(partialDeleteSuccessInput); + try { + result.catch((err) => { + expect(err).toEqual(partialDeleteSuccess); + done(); + }); + } catch (err) { + done(err); + } + } catch (err) { + done(err); + } + }) + + test('Invalid Input - missing table', async () => { + try { + const skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const result = await skyflow.delete({ records: [{ id: 'skyflow_id' }]}); + } catch (err) { + expect(err).toBeDefined(); + } + }) }); \ No newline at end of file diff --git a/test/vault-api/Util.test.js b/test/vault-api/Util.test.js index a3a3503..b5eb61f 100644 --- a/test/vault-api/Util.test.js +++ b/test/vault-api/Util.test.js @@ -3,12 +3,14 @@ import { validateUpdateInput, validateInsertRecords, validateInitConfig, - isValidURL + isValidURL, + validateDeleteInputAndOptions } from "../../src/vault-api/utils/validators"; import SKYFLOW_ERROR_CODE from "../../src/vault-api/utils/constants"; import { parameterizedString } from "../../src/vault-api/utils/logs-helper"; import SkyflowError from "../../src/vault-api/libs/SkyflowError"; import { fillUrlWithPathAndQueryParams, formatVaultURL, toLowerKeys, objectToFormData, generateSDKMetrics } from "../../src/vault-api/utils/helpers"; +import logs from "../../src/vault-api/utils/logs"; const FormData = require('form-data'); let mockJson = {}; @@ -588,9 +590,118 @@ describe("test generateSDKMetrics",()=>{ expect(metrics.sdk_client_os_details).toBe(''); }); +}); +describe('skyflow delete method validation tests', () => { + test('Invalid Input - null delete input', () => { + try { + validateDeleteInputAndOptions(null); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).toBe(logs.errorLogs.INVALID_DELETE_INPUT); + } + }) + + test('Invalid Input - missing records key', () => { + try { + validateDeleteInputAndOptions({}); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).toBe(logs.errorLogs.MISSING_RECORDS); + } + }) + + test('Invalid Input - invalid records key type', () => { + try { + validateDeleteInputAndOptions({ records: {}}); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).toBe(logs.errorLogs.INVLAID_DELETE_RECORDS_INPUT); + } + }) + test('Invalid Input - empty records array', () => { + try { + validateDeleteInputAndOptions({ records: []}); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).toBe(logs.errorLogs.EMPTY_RECORDS); + } + }) + test('Invalid Input - empty record in records', () => { + try { + validateDeleteInputAndOptions({ records: [{}]}); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).toBe(logs.errorLogs.EMPTY_RECORDS); + } + }) + test('Invalid Input - missing ID', () => { + try { + validateDeleteInputAndOptions({ records: [{ table: 'table' }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_ID_IN_DELETE, [0], true); -}); \ No newline at end of file + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + + test('Invalid Input - invalid ID 1', () => { + try { + validateDeleteInputAndOptions({ records: [{ id: 123 }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ID_IN_DELETE, [0], true); + + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + + test('Invalid Input - invalid ID 2', () => { + try { + validateDeleteInputAndOptions({ records: [{ id: ' ' }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ID_IN_DELETE, [0], true); + + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + + test('Invalid Input - missing table', () => { + try { + validateDeleteInputAndOptions({ records: [{ id: 'skyflow_id' }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TABLE_IN_DELETE, [0], true); + + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + + test('Invalid Input - invalid table 1', () => { + try { + validateDeleteInputAndOptions({ records: [{ id: 'skyflow_id', table: {} }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TABLE_IN_DELETE, [0], true); + + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + + test('Invalid Input - invalid table 2', () => { + try { + validateDeleteInputAndOptions({ records: [{ id: 'skyflow_id', table: ' ' }]}); + } catch (err) { + const skyflowError = new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TABLE_IN_DELETE, [0], true); + + expect(err).toBeDefined(); + expect(err.message).toBe(skyflowError.error.description); + } + }) + +});