diff --git a/src/utils/validations/index.ts b/src/utils/validations/index.ts index bd93831b..402f3b04 100644 --- a/src/utils/validations/index.ts +++ b/src/utils/validations/index.ts @@ -413,49 +413,43 @@ export const validateUpdateConnectionConfig = (connectionConfig: ConnectionConfi }; function validateInsertInput(input: unknown, index: number): void { - try { - const inputObject = input as { [key: string]: unknown }; - - // Check if the object is empty - const entries = Object.entries(inputObject); - - if (entries.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); - } + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); + } - for (const [key] of entries) { - if (key && typeof key !== 'string') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); - } - } + const inputObject = input as { [key: string]: unknown }; + const entries = Object.entries(inputObject); - } catch (error) { + if (entries.length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); } + for (const [key] of entries) { + if (!key || typeof key !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); + } + } } function validateUpdateInput(input: unknown): void { - try { - const inputObject = input as { [key: string]: unknown }; + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + } - // Check if the object is empty - const entries = Object.entries(inputObject); + const inputObject = input as { [key: string]: unknown }; - if (entries.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); - } + // Exclude skyflow_id — it is the record identifier, not a data field to update + const entries = Object.entries(inputObject).filter(([key]) => key !== SKYFLOW.ID); - for (const [key] of entries) { - if (key && typeof key !== 'string') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); - } - } - - } catch (error) { + if (entries.length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); } + for (const [key] of entries) { + if (!key || typeof key !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + } + } } function validateUpdateToken(input: unknown): void { diff --git a/src/vault/controller/vault/index.ts b/src/vault/controller/vault/index.ts index 6f1619b0..62f644ee 100644 --- a/src/vault/controller/vault/index.ts +++ b/src/vault/controller/vault/index.ts @@ -115,8 +115,8 @@ class VaultController { if (body && Array.isArray(body.records)) { body.records.forEach((field: StringKeyValueMapType) => { response.success.push({ - skyflowId: String(field?.skyflow_id), - requestIndex: index, + skyflow_id: String(field?.skyflow_id), + request_index: index, ...(typeof field?.tokens === 'object' && field?.tokens !== null ? field.tokens : {}) }); }); @@ -213,7 +213,7 @@ class VaultController { private parseBulkInsertResponse(records: Record[]): InsertResponse { const insertedFields: InsertResponseType[] = records.map(record => ({ - skyflowId: String(record.skyflow_id), + skyflow_id: String(record.skyflow_id), ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}) })); return new InsertResponse({ insertedFields, errors: null }); @@ -290,7 +290,7 @@ class VaultController { ).then(data => { printLog(logs.infoLogs.UPDATE_SUCCESS, MessageType.LOG, this.client.getLogLevel()); const updatedRecord = { - skyflowId: data.skyflow_id, + skyflow_id: data.skyflow_id, ...data?.tokens }; resolve(new UpdateResponse({ updatedField: updatedRecord, errors: null })); @@ -490,7 +490,7 @@ class VaultController { printLog(logs.infoLogs.QUERY_SUCCESS, MessageType.LOG, this.client.getLogLevel()); const processedRecords = response.records.map(record => ({ ...(typeof record.fields === 'object' && record.fields !== null ? record.fields : {}), - tokenizedData: { + tokenized_data: { ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}), }, })); diff --git a/src/vault/model/response/invoke/invoke.ts b/src/vault/model/response/invoke/invoke.ts index 66aab362..617bb708 100644 --- a/src/vault/model/response/invoke/invoke.ts +++ b/src/vault/model/response/invoke/invoke.ts @@ -1,7 +1,6 @@ //imports import { SkyflowRecordError } from "../../../../utils"; -import { QueryResponseType } from "../../../types"; class InvokeConnectionResponse { //fields diff --git a/src/vault/skyflow/index.ts b/src/vault/skyflow/index.ts index c65b549e..b1fef8fd 100644 --- a/src/vault/skyflow/index.ts +++ b/src/vault/skyflow/index.ts @@ -186,6 +186,15 @@ class Skyflow { this.updateClients(CONFIG.LOGLEVEL); } + updateLogLevel(logLevel: LogLevel): Skyflow { + if (logLevel && !isLogLevel(logLevel)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_LOG_LEVEL); + } + this.logLevel = logLevel; + this.updateClients(CONFIG.LOGLEVEL); + return this; + } + getLogLevel() { return this.logLevel; } diff --git a/src/vault/types/index.ts b/src/vault/types/index.ts index e68f84ef..36c5e997 100644 --- a/src/vault/types/index.ts +++ b/src/vault/types/index.ts @@ -40,7 +40,7 @@ export interface ClientObj { } export interface InsertResponseType { - skyflowId: string; + skyflow_id: string; [key: string]: unknown; } diff --git a/test/utils/validations.test.js b/test/utils/validations.test.js index 22880d0a..f360c874 100644 --- a/test/utils/validations.test.js +++ b/test/utils/validations.test.js @@ -1164,7 +1164,6 @@ describe('validateInsertRequest', () => { }); // Test valid cases - // Test valid cases test('should accept valid insert request', () => { const request = { _table: 'users', // Changed from table to _table @@ -1177,6 +1176,52 @@ test('should accept valid insert request', () => { expect(() => validateInsertRequest(request)).not.toThrow(); }); +test('should accept insert request with null field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: null } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with empty string field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with mixed null, empty string and valid field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '4111111111111112', cvv: null, expiry: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with multiple records containing null and empty values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '4111111111111112' }, + { card_number: null }, + { card_number: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + // Also update other test cases that check table property test('should throw error when table is missing', () => { const request = { @@ -1421,17 +1466,56 @@ describe('validateUpdateRequest - validateUpdateInput', () => { .toThrow(SKYFLOW_ERROR_CODE.INVALID_TYPE_OF_UPDATE_DATA); }); - // Test validateUpdateInput with null values - test('should throw error when data contains null values', () => { + // Test validateUpdateInput with null values — should be accepted + test('should accept update data with null field values', () => { const request = { ...validTable, data: { - skyflow_id: 'valid-id', + skyflowId: 'valid-id', field: null } }; - expect(() => validateUpdateRequest(request)) - .toThrow(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with empty string field values — should be accepted + test('should accept update data with empty string field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + field: '' + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with mixed null, empty string and valid values + test('should accept update data with mixed null, empty string and valid field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + card_number: null, + name: '', + ssn: '123-45-6789' + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with numeric and boolean field values + test('should accept update data with numeric and boolean field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + age: 0, + active: false, + score: 99.5 + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); }); // Test valid update data with multiple fields diff --git a/test/vault/controller/vault.test.js b/test/vault/controller/vault.test.js index df6a8ea6..03ef4de2 100644 --- a/test/vault/controller/vault.test.js +++ b/test/vault/controller/vault.test.js @@ -1007,7 +1007,7 @@ describe('VaultController query method', () => { expect(response).toBeInstanceOf(QueryResponse); expect(response.fields).toHaveLength(1); expect(response.fields[0].id).toBe('1'); - expect(response.fields[0].tokenizedData.id).toBe('token123'); + expect(response.fields[0].tokenized_data.id).toBe('token123'); expect(response.errors).toBe(null); }); @@ -1154,7 +1154,7 @@ describe('VaultController update method', () => { expect.any(Object) // Headers ); expect(response).toBeInstanceOf(UpdateResponse); - expect(response.updatedField.skyflowId).toBe('id123'); + expect(response.updatedField.skyflow_id).toBe('id123'); expect(response.updatedField.field1).toBe('token123'); expect(response.errors).toBeNull(); }); @@ -1185,7 +1185,7 @@ describe('VaultController update method', () => { expect.any(Object) // Headers ); expect(response).toBeInstanceOf(UpdateResponse); - expect(response.updatedField.skyflowId).toBe('id123'); + expect(response.updatedField.skyflow_id).toBe('id123'); expect(response.updatedField.field1).toBe('token123'); expect(response.errors).toBeNull(); }); @@ -1219,7 +1219,7 @@ describe('VaultController update method', () => { expect.any(Object) // Headers ); expect(response).toBeInstanceOf(UpdateResponse); - expect(response.updatedField.skyflowId).toBe('id123'); + expect(response.updatedField.skyflow_id).toBe('id123'); expect(response.updatedField.field1).toBe('token123'); expect(response.errors).toBeNull(); }); diff --git a/test/vault/skyflow/skyflow.test.js b/test/vault/skyflow/skyflow.test.js index 2a5ca9b7..48ddb183 100644 --- a/test/vault/skyflow/skyflow.test.js +++ b/test/vault/skyflow/skyflow.test.js @@ -190,6 +190,68 @@ describe('Skyflow initialization', () => { expect(() => skyflow.setLogLevel("DUMMY")) .toThrowError(invalidLogLevelError); }); + + test('should update log level using updateLogLevel and return Skyflow instance', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const result = skyflow.updateLogLevel(LogLevel.DEBUG); + expect(skyflow.getLogLevel()).toBe(LogLevel.DEBUG); + expect(result).toBeInstanceOf(Skyflow); + }); + + test('should support method chaining with updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const result = skyflow.updateLogLevel(LogLevel.INFO); + expect(result).toBe(skyflow); + }); + + test('should throw error when updateLogLevel is called with invalid logLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + expect(() => skyflow.updateLogLevel("INVALID")) + .toThrowError(invalidLogLevelError); + }); + + test('should propagate updated log level to all vault clients via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: [ + { vaultId: "VAULT_ID_1", clusterId: "CLUSTER_ID" }, + { vaultId: "VAULT_ID_2", clusterId: "CLUSTER_ID" } + ], + logLevel: LogLevel.ERROR + }); + skyflow.updateLogLevel(LogLevel.WARN); + expect(skyflow.getLogLevel()).toBe(LogLevel.WARN); + }); + + test('should propagate updated log level to vault and connection clients via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: [{ vaultId: "VAULT_ID", clusterId: "CLUSTER_ID" }], + connectionConfigs: [{ connectionId: "CONN_ID", connectionUrl: "https://conn.com" }], + logLevel: LogLevel.ERROR + }); + skyflow.updateLogLevel(LogLevel.OFF); + expect(skyflow.getLogLevel()).toBe(LogLevel.OFF); + }); + + test('should update log level to all valid LogLevel values via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.OFF]; + levels.forEach(level => { + skyflow.updateLogLevel(level); + expect(skyflow.getLogLevel()).toBe(level); + }); + }); }); describe('Skyflow Credentials Tests', () => {