From 6fa9f3f5c177ddc56b197997bc2fbbef44a0f57f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B7=E8=8B=A5=E9=9C=9C=E5=AF=92?= <912881342@qq.com> Date: Tue, 22 Sep 2020 16:39:43 +0800 Subject: [PATCH] feat: inventory (#860) --- .prettierrc | 2 +- README.md | 162 +++++++++++++ lib/browser/client.js | 6 + lib/common/bucket/deleteBucketInventory.d.ts | 10 + lib/common/bucket/deleteBucketInventory.js | 22 ++ lib/common/bucket/deleteBucketInventory.ts | 21 ++ lib/common/bucket/getBucketInventory.d.ts | 11 + lib/common/bucket/getBucketInventory.js | 25 +++ lib/common/bucket/getBucketInventory.ts | 24 ++ lib/common/bucket/index.js | 4 + lib/common/bucket/listBucketInventory.d.ts | 13 ++ lib/common/bucket/listBucketInventory.js | 29 +++ lib/common/bucket/listBucketInventory.ts | 28 +++ lib/common/bucket/putBucketInventory.d.ts | 36 +++ lib/common/bucket/putBucketInventory.js | 58 +++++ lib/common/bucket/putBucketInventory.ts | 84 +++++++ lib/common/utils/checkBucketName.d.ts | 2 +- lib/common/utils/checkBucketName.js | 2 +- lib/common/utils/checkBucketName.ts | 2 +- lib/common/utils/dataFix.d.ts | 12 + lib/common/utils/dataFix.js | 65 ++++++ lib/common/utils/dataFix.ts | 86 +++++++ lib/common/utils/formatInventoryConfig.d.ts | 1 + lib/common/utils/formatInventoryConfig.js | 45 ++++ lib/common/utils/formatInventoryConfig.ts | 40 ++++ lib/common/utils/formatObjKey.d.ts | 7 +- lib/common/utils/formatObjKey.js | 11 +- lib/common/utils/formatObjKey.ts | 16 +- test/node/bucket.test.js | 162 ++++++++++++- test/node/dataFix.test.js | 225 +++++++++++++++++++ 30 files changed, 1196 insertions(+), 15 deletions(-) create mode 100644 lib/common/bucket/deleteBucketInventory.d.ts create mode 100644 lib/common/bucket/deleteBucketInventory.js create mode 100644 lib/common/bucket/deleteBucketInventory.ts create mode 100644 lib/common/bucket/getBucketInventory.d.ts create mode 100644 lib/common/bucket/getBucketInventory.js create mode 100644 lib/common/bucket/getBucketInventory.ts create mode 100644 lib/common/bucket/listBucketInventory.d.ts create mode 100644 lib/common/bucket/listBucketInventory.js create mode 100644 lib/common/bucket/listBucketInventory.ts create mode 100644 lib/common/bucket/putBucketInventory.d.ts create mode 100644 lib/common/bucket/putBucketInventory.js create mode 100644 lib/common/bucket/putBucketInventory.ts create mode 100644 lib/common/utils/dataFix.d.ts create mode 100644 lib/common/utils/dataFix.js create mode 100644 lib/common/utils/dataFix.ts create mode 100644 lib/common/utils/formatInventoryConfig.d.ts create mode 100644 lib/common/utils/formatInventoryConfig.js create mode 100644 lib/common/utils/formatInventoryConfig.ts create mode 100644 test/node/dataFix.test.js diff --git a/.prettierrc b/.prettierrc index 6ed6b425e..5675a48f0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,7 +3,7 @@ "singleQuote": true, "tabWidth": 2, "useTabs": false, - "printWidth": 80, + "printWidth": 120, "bracketSpacing": true, "arrowParens": "avoid" } diff --git a/README.md b/README.md index f50cd97ba..ab39bb19e 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,11 @@ All operation use es7 async/await to implement. All api is async function. - versioning - [.getBucketVersioning(name, [, options])](#getBucketVersioningname-options) - [.putBucketVersioning(name, status[, options])](#putBucketVersioningname-status-options) + - inventory + - [.getBucketInventory(name, inventoryId[, options])](#getBucketInventoryname-inventoryid-options) + - [.putBucketInventory(name, inventory[, options])](#putBucketInventoryname-inventory-options) + - [.deleteBucketInventory(name, inventoryId[, options])](#deleteBucketInventoryname-inventoryid-options) + - [.listBucketInventory(name, [, options])](#listBucketInventoryname-options) - [Object Operations](#object-operations) - [.list(query[, options])](#listquery-options) @@ -1259,6 +1264,163 @@ Success will return: --- + +### .getBucketInventory(name, inventoryId[, options]) + +get bucket inventory by inventory-id + +parameters: + +- name {String} the bucket name +- inventoryId {String} inventory-id +- [options] {Object} optional args + +Success will return: + +- inventory {Inventory} +- status {Number} response status +- res {Object} response info + +```js +async function getBucketInventoryById() { + try { + const result = await client.getBucketInventory('bucket', 'inventoryid'); + console.log(result.inventory) + } catch (err) { + console.log(err) + } +} + +getBucketInventoryById(); +``` + +### putBucketInventory(name, inventory[, options]) + +set bucket inventory + +parameters: + +- name {String} the bucket name +- inventory {Inventory} inventory config +- [options] {Object} optional args + +Success will return: + +- status {Number} response status +- res {Object} response info + +```ts +type Field = 'Size | LastModifiedDate | ETag | StorageClass | IsMultipartUploaded | EncryptionStatus'; +interface Inventory { + id: string; + isEnabled: true | false; + prefix?: string; + OSSBucketDestination: { + format: 'CSV'; + accountId: string; + rolename: string; + bucket: string; + prefix?: string; + encryption?: + | {'SSE-OSS': ''} + | { + 'SSE-KMS': { + keyId: string; + }; + }; + }; + frequency: 'Daily' | 'Weekly'; + includedObjectVersions: 'Current' | 'All'; + optionalFields?: { + field?: Field[]; + }; +} +``` +```js +const inventory = { + id: 'default', + isEnabled: false, // `true` | `false` + prefix: 'ttt', // filter prefix + OSSBucketDestination: { + format: 'CSV', + accountId: '1817184078010220', + rolename: 'AliyunOSSRole', + bucket: 'your bucket', + prefix: 'test', + //encryption: {'SSE-OSS': ''}, + /* + encryption: { + 'SSE-KMS': { + keyId: 'test-kms-id'; + };, + */ + }, + frequency: 'Daily', // `WEEKLY` | `Daily` + includedObjectVersions: 'All', // `All` | `Current` + optionalFields: { + field: ["Size", "LastModifiedDate", "ETag", "StorageClass", "IsMultipartUploaded", "EncryptionStatus"] + }, +} + +async function putInventory(){ + const bucket = 'Your Bucket Name'; + try { + await client.putBucketInventory(bucket, inventory); + } catch(err) { + console.log(err); + } +} + +putInventory() +``` + +### deleteBucketInventory(name, inventoryId[, options]) + +delete bucket inventory by inventory-id + +parameters: + +- name {String} the bucket name +- inventoryId {String} inventory-id +- [options] {Object} optional args + +Success will return: + +- status {Number} response status +- res {Object} response info + +### listBucketInventory(name[, options]) + +list bucket inventory + +parameters: + +- name {String} the bucket name +- [options] {Object} optional args + - continuationToken used by search next page + +Success will return: + +- status {Number} response status +- res {Object} response info + +example: + +```js +async function listBucketInventory() { + const bucket = 'Your Bucket Name'; + let nextContinuationToken; + // list all inventory of the bucket + do { + const result = await client.listBucketInventory(bucket, nextContinuationToken); + console.log(result.inventoryList); + nextContinuationToken = result.nextContinuationToken; + } while (nextContinuationToken) +} + +listBucketInventory(); +``` + ## Object Operations All operations function return Promise, except `signatureUrl`. diff --git a/lib/browser/client.js b/lib/browser/client.js index 9d80a4a27..755911cc2 100644 --- a/lib/browser/client.js +++ b/lib/browser/client.js @@ -105,6 +105,12 @@ merge(proto, require('../common/bucket/deleteBucketLifecycle')); merge(proto, require('../common/bucket/putBucketVersioning')); merge(proto, require('../common/bucket/getBucketVersioning')); +// inventory +merge(proto, require('../common/bucket/getBucketInventory')); +merge(proto, require('../common/bucket/deleteBucketInventory')); +merge(proto, require('../common/bucket/listBucketInventory')); +merge(proto, require('../common/bucket/putBucketInventory')); + // multipart upload merge(proto, require('./managed-upload')); /** diff --git a/lib/common/bucket/deleteBucketInventory.d.ts b/lib/common/bucket/deleteBucketInventory.d.ts new file mode 100644 index 000000000..5eead0d99 --- /dev/null +++ b/lib/common/bucket/deleteBucketInventory.d.ts @@ -0,0 +1,10 @@ +/** + * deleteBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +export declare function deleteBucketInventory(this: any, bucketName: string, inventoryId: string, options?: any): Promise<{ + status: any; + res: any; +}>; diff --git a/lib/common/bucket/deleteBucketInventory.js b/lib/common/bucket/deleteBucketInventory.js new file mode 100644 index 000000000..458901717 --- /dev/null +++ b/lib/common/bucket/deleteBucketInventory.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.deleteBucketInventory = void 0; +const checkBucketName_1 = require("../utils/checkBucketName"); +/** + * deleteBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +async function deleteBucketInventory(bucketName, inventoryId, options = {}) { + const subres = Object.assign({ inventory: '', inventoryId }, options.subres); + checkBucketName_1.checkBucketName(bucketName); + const params = this._bucketRequestParams('DELETE', bucketName, subres, options); + params.successStatuses = [204]; + const result = await this.request(params); + return { + status: result.status, + res: result.res, + }; +} +exports.deleteBucketInventory = deleteBucketInventory; diff --git a/lib/common/bucket/deleteBucketInventory.ts b/lib/common/bucket/deleteBucketInventory.ts new file mode 100644 index 000000000..a04efdbea --- /dev/null +++ b/lib/common/bucket/deleteBucketInventory.ts @@ -0,0 +1,21 @@ +import { checkBucketName } from '../utils/checkBucketName'; +/** + * deleteBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ + +export async function deleteBucketInventory(this: any, bucketName: string, inventoryId: string, options: any = {}) { + const subres: any = Object.assign({ inventory: '', inventoryId }, options.subres); + checkBucketName(bucketName); + + const params = this._bucketRequestParams('DELETE', bucketName, subres, options); + params.successStatuses = [204]; + + const result = await this.request(params); + return { + status: result.status, + res: result.res, + }; +} diff --git a/lib/common/bucket/getBucketInventory.d.ts b/lib/common/bucket/getBucketInventory.d.ts new file mode 100644 index 000000000..8cf2bee5d --- /dev/null +++ b/lib/common/bucket/getBucketInventory.d.ts @@ -0,0 +1,11 @@ +/** + * getBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +export declare function getBucketInventory(this: any, bucketName: string, inventoryId: string, options?: any): Promise<{ + status: any; + res: any; + inventory: any; +}>; diff --git a/lib/common/bucket/getBucketInventory.js b/lib/common/bucket/getBucketInventory.js new file mode 100644 index 000000000..f5e0f40b5 --- /dev/null +++ b/lib/common/bucket/getBucketInventory.js @@ -0,0 +1,25 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getBucketInventory = void 0; +const checkBucketName_1 = require("../utils/checkBucketName"); +const formatInventoryConfig_1 = require("../utils/formatInventoryConfig"); +/** + * getBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +async function getBucketInventory(bucketName, inventoryId, options = {}) { + const subres = Object.assign({ inventory: '', inventoryId }, options.subres); + checkBucketName_1.checkBucketName(bucketName); + const params = this._bucketRequestParams('GET', bucketName, subres, options); + params.successStatuses = [200]; + params.xmlResponse = true; + const result = await this.request(params); + return { + status: result.status, + res: result.res, + inventory: formatInventoryConfig_1.formatInventoryConfig(result.data) + }; +} +exports.getBucketInventory = getBucketInventory; diff --git a/lib/common/bucket/getBucketInventory.ts b/lib/common/bucket/getBucketInventory.ts new file mode 100644 index 000000000..65c00c9a3 --- /dev/null +++ b/lib/common/bucket/getBucketInventory.ts @@ -0,0 +1,24 @@ +import { checkBucketName } from '../utils/checkBucketName'; +import { formatInventoryConfig } from '../utils/formatInventoryConfig'; +/** + * getBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ + +export async function getBucketInventory(this: any, bucketName: string, inventoryId: string, options: any = {}) { + const subres: any = Object.assign({ inventory: '', inventoryId }, options.subres); + checkBucketName(bucketName); + + const params = this._bucketRequestParams('GET', bucketName, subres, options); + params.successStatuses = [200]; + + params.xmlResponse = true; + const result = await this.request(params); + return { + status: result.status, + res: result.res, + inventory: formatInventoryConfig(result.data) + }; +} diff --git a/lib/common/bucket/index.js b/lib/common/bucket/index.js index 3a36f455a..6b66230a7 100644 --- a/lib/common/bucket/index.js +++ b/lib/common/bucket/index.js @@ -22,3 +22,7 @@ merge(proto, require('./putBucketPolicy')); merge(proto, require('./deleteBucketPolicy')); merge(proto, require('./getBucketVersioning')); merge(proto, require('./putBucketVersioning')); +merge(proto, require('./getBucketInventory')); +merge(proto, require('./deleteBucketInventory')); +merge(proto, require('./listBucketInventory')); +merge(proto, require('./putBucketInventory')); diff --git a/lib/common/bucket/listBucketInventory.d.ts b/lib/common/bucket/listBucketInventory.d.ts new file mode 100644 index 000000000..5800a1a5a --- /dev/null +++ b/lib/common/bucket/listBucketInventory.d.ts @@ -0,0 +1,13 @@ +/** + * listBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +export declare function listBucketInventory(this: any, bucketName: string, options?: any): Promise<{ + isTruncated: boolean; + nextContinuationToken: any; + inventoryList: any; + status: any; + res: any; +}>; diff --git a/lib/common/bucket/listBucketInventory.js b/lib/common/bucket/listBucketInventory.js new file mode 100644 index 000000000..cdc050190 --- /dev/null +++ b/lib/common/bucket/listBucketInventory.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.listBucketInventory = void 0; +const checkBucketName_1 = require("../utils/checkBucketName"); +const formatInventoryConfig_1 = require("../utils/formatInventoryConfig"); +/** + * listBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ +async function listBucketInventory(bucketName, options = {}) { + const { continuationToken } = options; + const subres = Object.assign({ inventory: '' }, continuationToken && { 'continuation-token': continuationToken }, options.subres); + checkBucketName_1.checkBucketName(bucketName); + const params = this._bucketRequestParams('GET', bucketName, subres, options); + params.successStatuses = [200]; + params.xmlResponse = true; + const result = await this.request(params); + const { data, res, status } = result; + return { + isTruncated: data.IsTruncated === 'true', + nextContinuationToken: data.NextContinuationToken, + inventoryList: formatInventoryConfig_1.formatInventoryConfig(data.InventoryConfiguration, true), + status, + res, + }; +} +exports.listBucketInventory = listBucketInventory; diff --git a/lib/common/bucket/listBucketInventory.ts b/lib/common/bucket/listBucketInventory.ts new file mode 100644 index 000000000..cc4f3558a --- /dev/null +++ b/lib/common/bucket/listBucketInventory.ts @@ -0,0 +1,28 @@ +import { checkBucketName } from '../utils/checkBucketName'; +import { formatInventoryConfig } from '../utils/formatInventoryConfig'; +/** + * listBucketInventory + * @param {String} bucketName - bucket name + * @param {String} inventoryId + * @param {Object} options + */ + +export async function listBucketInventory(this: any, bucketName: string, options: any = {}) { + const { continuationToken } = options; + const subres: any = Object.assign({ inventory: '' }, continuationToken && { 'continuation-token': continuationToken }, options.subres); + checkBucketName(bucketName); + + const params = this._bucketRequestParams('GET', bucketName, subres, options); + params.successStatuses = [200]; + + params.xmlResponse = true; + const result = await this.request(params); + const { data, res, status } = result; + return { + isTruncated: data.IsTruncated === 'true', + nextContinuationToken: data.NextContinuationToken, + inventoryList: formatInventoryConfig(data.InventoryConfiguration, true), + status, + res, + }; +} diff --git a/lib/common/bucket/putBucketInventory.d.ts b/lib/common/bucket/putBucketInventory.d.ts new file mode 100644 index 000000000..006faca52 --- /dev/null +++ b/lib/common/bucket/putBucketInventory.d.ts @@ -0,0 +1,36 @@ +declare type Field = 'Size | LastModifiedDate | ETag | StorageClass | IsMultipartUploaded | EncryptionStatus'; +interface Inventory { + id: string; + isEnabled: true | false; + prefix?: string; + OSSBucketDestination: { + format: 'CSV'; + accountId: string; + rolename: string; + bucket: string; + prefix?: string; + encryption?: { + 'SSE-OSS': ''; + } | { + 'SSE-KMS': { + keyId: string; + }; + }; + }; + frequency: 'Daily' | 'Weekly'; + includedObjectVersions: 'Current' | 'All'; + optionalFields?: { + field?: Field[]; + }; +} +/** + * putBucketInventory + * @param {String} bucketName - bucket name + * @param {Inventory} inventory + * @param {Object} options + */ +export declare function putBucketInventory(this: any, bucketName: string, inventory: Inventory, options?: any): Promise<{ + status: any; + res: any; +}>; +export {}; diff --git a/lib/common/bucket/putBucketInventory.js b/lib/common/bucket/putBucketInventory.js new file mode 100644 index 000000000..fbdc5159e --- /dev/null +++ b/lib/common/bucket/putBucketInventory.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.putBucketInventory = void 0; +const checkBucketName_1 = require("../utils/checkBucketName"); +const obj2xml_1 = require("../utils/obj2xml"); +/** + * putBucketInventory + * @param {String} bucketName - bucket name + * @param {Inventory} inventory + * @param {Object} options + */ +async function putBucketInventory(bucketName, inventory, options = {}) { + const subres = Object.assign({ inventory: '', inventoryId: inventory.id }, options.subres); + checkBucketName_1.checkBucketName(bucketName); + const { OSSBucketDestination, optionalFields, includedObjectVersions } = inventory; + const destinationBucketPrefix = 'acs:oss:::'; + const rolePrefix = `acs:ram::${OSSBucketDestination.accountId}:role/`; + const paramXMLObj = { + InventoryConfiguration: { + Id: inventory.id, + IsEnabled: inventory.isEnabled, + Filter: { + Prefix: inventory.prefix || '', + }, + Destination: { + OSSBucketDestination: { + Format: OSSBucketDestination.format, + AccountId: OSSBucketDestination.accountId, + RoleArn: `${rolePrefix}${OSSBucketDestination.rolename}`, + Bucket: `${destinationBucketPrefix}${OSSBucketDestination.bucket}`, + Prefix: OSSBucketDestination.prefix || '', + Encryption: OSSBucketDestination.encryption || '', + }, + }, + Schedule: { + Frequency: inventory.frequency, + }, + IncludedObjectVersions: includedObjectVersions, + OptionalFields: { + Field: (optionalFields === null || optionalFields === void 0 ? void 0 : optionalFields.field) || [], + }, + }, + }; + const paramXML = obj2xml_1.obj2xml(paramXMLObj, { + headers: true, + firstUpperCase: true, + }); + const params = this._bucketRequestParams('PUT', bucketName, subres, options); + params.successStatuses = [200]; + params.mime = 'xml'; + params.content = paramXML; + const result = await this.request(params); + return { + status: result.status, + res: result.res, + }; +} +exports.putBucketInventory = putBucketInventory; diff --git a/lib/common/bucket/putBucketInventory.ts b/lib/common/bucket/putBucketInventory.ts new file mode 100644 index 000000000..c7793e68b --- /dev/null +++ b/lib/common/bucket/putBucketInventory.ts @@ -0,0 +1,84 @@ +import { checkBucketName } from '../utils/checkBucketName'; +import { obj2xml } from '../utils/obj2xml'; + +type Field = 'Size | LastModifiedDate | ETag | StorageClass | IsMultipartUploaded | EncryptionStatus'; + +interface Inventory { + id: string; + isEnabled: true | false; + prefix?: string; + OSSBucketDestination: { + format: 'CSV'; + accountId: string; + rolename: string; + bucket: string; + prefix?: string; + encryption?: + | {'SSE-OSS': ''} + | { + 'SSE-KMS': { + keyId: string; + }; + }; + }; + frequency: 'Daily' | 'Weekly'; + includedObjectVersions: 'Current' | 'All'; + optionalFields?: { + field?: Field[]; + }; +} + +/** + * putBucketInventory + * @param {String} bucketName - bucket name + * @param {Inventory} inventory + * @param {Object} options + */ + +export async function putBucketInventory(this: any, bucketName: string, inventory: Inventory, options: any = {}) { + const subres: any = Object.assign({ inventory: '', inventoryId: inventory.id }, options.subres); + checkBucketName(bucketName); + const { OSSBucketDestination, optionalFields, includedObjectVersions } = inventory; + const destinationBucketPrefix = 'acs:oss:::'; + const rolePrefix = `acs:ram::${OSSBucketDestination.accountId}:role/`; + const paramXMLObj: any = { + InventoryConfiguration: { + Id: inventory.id, + IsEnabled: inventory.isEnabled, + Filter: { + Prefix: inventory.prefix || '', + }, + Destination: { + OSSBucketDestination: { + Format: OSSBucketDestination.format, + AccountId: OSSBucketDestination.accountId, + RoleArn: `${rolePrefix}${OSSBucketDestination.rolename}`, + Bucket: `${destinationBucketPrefix}${OSSBucketDestination.bucket}`, + Prefix: OSSBucketDestination.prefix || '', + Encryption: OSSBucketDestination.encryption || '', + }, + }, + Schedule: { + Frequency: inventory.frequency, + }, + IncludedObjectVersions: includedObjectVersions, + OptionalFields: { + Field: optionalFields?.field || [], + }, + }, + }; + const paramXML = obj2xml(paramXMLObj, { + headers: true, + firstUpperCase: true, + }); + + const params = this._bucketRequestParams('PUT', bucketName, subres, options); + params.successStatuses = [200]; + params.mime = 'xml'; + params.content = paramXML; + const result = await this.request(params); + return { + status: result.status, + res: result.res, + }; +} diff --git a/lib/common/utils/checkBucketName.d.ts b/lib/common/utils/checkBucketName.d.ts index 3146071b0..4de489dc7 100644 --- a/lib/common/utils/checkBucketName.d.ts +++ b/lib/common/utils/checkBucketName.d.ts @@ -1 +1 @@ -export declare const checkBucketName: (name: string, createBucket: boolean) => void; +export declare const checkBucketName: (name: string, createBucket?: boolean) => void; diff --git a/lib/common/utils/checkBucketName.js b/lib/common/utils/checkBucketName.js index faf9b8425..9d25c6263 100644 --- a/lib/common/utils/checkBucketName.js +++ b/lib/common/utils/checkBucketName.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkBucketName = void 0; -exports.checkBucketName = (name, createBucket) => { +exports.checkBucketName = (name, createBucket = false) => { const bucketRegex = createBucket ? /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/ : /^[a-z0-9_][a-z0-9-_]{1,61}[a-z0-9_]$/; if (!bucketRegex.test(name)) { throw new Error('The bucket must be conform to the specifications'); diff --git a/lib/common/utils/checkBucketName.ts b/lib/common/utils/checkBucketName.ts index 7fe7e91a4..b2d6c80b6 100644 --- a/lib/common/utils/checkBucketName.ts +++ b/lib/common/utils/checkBucketName.ts @@ -1,4 +1,4 @@ -export const checkBucketName = (name: string, createBucket: boolean): void => { +export const checkBucketName = (name: string, createBucket = false): void => { const bucketRegex = createBucket ? /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/ : /^[a-z0-9_][a-z0-9-_]{1,61}[a-z0-9_]$/; if (!bucketRegex.test(name)) { throw new Error('The bucket must be conform to the specifications'); diff --git a/lib/common/utils/dataFix.d.ts b/lib/common/utils/dataFix.d.ts new file mode 100644 index 000000000..da522adc2 --- /dev/null +++ b/lib/common/utils/dataFix.d.ts @@ -0,0 +1,12 @@ +interface Rename { + [propName: string]: string; +} +interface Config { + lowerFirst?: boolean; + rename?: Rename; + remove?: string[]; + camel?: string[]; + bool?: string[]; +} +export declare function dataFix(o: object, conf: Config, finalKill?: Function): typeof dataFix | undefined; +export {}; diff --git a/lib/common/utils/dataFix.js b/lib/common/utils/dataFix.js new file mode 100644 index 000000000..e132bdc61 --- /dev/null +++ b/lib/common/utils/dataFix.js @@ -0,0 +1,65 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.dataFix = void 0; +const isObject_1 = require("./isObject"); +const TRUE = ['true', 'TRUE', '1', 1]; +const FALSE = ['false', 'FALSE', '0', 0]; +function dataFix(o, conf, finalKill) { + if (!isObject_1.isObject(o)) + return; + const { remove = [], rename = {}, camel = [], bool = [], lowerFirst = false, } = conf; + // 删除不需要的数据 + remove.forEach(v => delete o[v]); + // 重命名 + Object.entries(rename).forEach(v => { + if (!o[v[0]]) + return; + if (o[v[1]]) + return; + o[v[1]] = o[v[0]]; + delete o[v[0]]; + }); + // 驼峰化 + camel.forEach(v => { + if (!o[v]) + return; + const afterKey = v + .replace(/^(.)/, $0 => $0.toLowerCase()) + .replace(/-(\w)/g, (_, $1) => $1.toUpperCase()); + if (o[afterKey]) + return; + o[afterKey] = o[v]; + // todo 暂时兼容以前数据,不做删除 + // delete o[v]; + }); + // 转换值为布尔值 + bool.forEach(v => { + o[v] = fixBool(o[v]); + }); + // finalKill + if (typeof finalKill === 'function') { + finalKill(o); + } + // 首字母转小写 + fixLowerFirst(o, lowerFirst); + return dataFix; +} +exports.dataFix = dataFix; +function fixBool(value) { + if (!value) + return false; + if (TRUE.includes(value)) + return true; + return FALSE.includes(value) ? false : value; +} +function fixLowerFirst(o, lowerFirst) { + if (lowerFirst) { + Object.keys(o).forEach(key => { + const lowerK = key.replace(/^\w/, match => match.toLowerCase()); + if (typeof o[lowerK] === 'undefined') { + o[lowerK] = o[key]; + delete o[key]; + } + }); + } +} diff --git a/lib/common/utils/dataFix.ts b/lib/common/utils/dataFix.ts new file mode 100644 index 000000000..8c1b73342 --- /dev/null +++ b/lib/common/utils/dataFix.ts @@ -0,0 +1,86 @@ +import { isObject } from './isObject'; + +interface Rename { + [propName: string]: string; +} + +interface Config { + lowerFirst?: boolean; + rename?: Rename; + remove?: string[]; + camel?: string[]; + bool?: string[]; +} + +const TRUE = ['true', 'TRUE', '1', 1]; +const FALSE = ['false', 'FALSE', '0', 0]; + +export function dataFix(o: object, conf: Config, finalKill?: Function) { + if (!isObject(o)) return; + + const { + remove = [], + rename = {}, + camel = [], + bool = [], + lowerFirst = false, + } = conf; + + // 删除不需要的数据 + remove.forEach(v => delete o[v]); + + // 重命名 + Object.entries(rename).forEach(v => { + if (!o[v[0]]) return; + if (o[v[1]]) return; + o[v[1]] = o[v[0]]; + delete o[v[0]]; + }); + + // 驼峰化 + camel.forEach(v => { + if (!o[v]) return; + const afterKey = v + .replace(/^(.)/, $0 => $0.toLowerCase()) + .replace(/-(\w)/g, (_, $1) => $1.toUpperCase()); + if (o[afterKey]) return; + o[afterKey] = o[v]; + // todo 暂时兼容以前数据,不做删除 + // delete o[v]; + }); + + // 转换值为布尔值 + bool.forEach(v => { + o[v] = fixBool(o[v]); + }); + + // finalKill + if (typeof finalKill === 'function') { + finalKill(o); + } + + // 首字母转小写 + fixLowerFirst(o, lowerFirst); + + return dataFix; +} + +function fixBool(value) { + if (!value) return false; + + if (TRUE.includes(value)) return true; + + return FALSE.includes(value) ? false : value; +} + +function fixLowerFirst(o, lowerFirst) { + if (lowerFirst) { + Object.keys(o).forEach(key => { + const lowerK = key.replace(/^\w/, match => match.toLowerCase()); + if (typeof o[lowerK] === 'undefined') { + o[lowerK] = o[key]; + delete o[key]; + } + }); + } +} diff --git a/lib/common/utils/formatInventoryConfig.d.ts b/lib/common/utils/formatInventoryConfig.d.ts new file mode 100644 index 000000000..2210338ae --- /dev/null +++ b/lib/common/utils/formatInventoryConfig.d.ts @@ -0,0 +1 @@ +export declare function formatInventoryConfig(inventoryConfig: any, toArray?: boolean): any; diff --git a/lib/common/utils/formatInventoryConfig.js b/lib/common/utils/formatInventoryConfig.js new file mode 100644 index 000000000..a9f8b755a --- /dev/null +++ b/lib/common/utils/formatInventoryConfig.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.formatInventoryConfig = void 0; +const dataFix_1 = require("../utils/dataFix"); +const isObject_1 = require("../utils/isObject"); +const isArray_1 = require("../utils/isArray"); +const formatObjKey_1 = require("../utils/formatObjKey"); +function formatInventoryConfig(inventoryConfig, toArray = false) { + if (toArray && isObject_1.isObject(inventoryConfig)) + inventoryConfig = [inventoryConfig]; + if (isArray_1.isArray(inventoryConfig)) { + inventoryConfig = inventoryConfig.map(formatFn); + } + else { + inventoryConfig = formatFn(inventoryConfig); + } + return inventoryConfig; +} +exports.formatInventoryConfig = formatInventoryConfig; +function formatFn(_) { + dataFix_1.dataFix(_, { bool: ['IsEnabled'] }, conf => { + var _a, _b; + // prefix + conf.prefix = conf.Filter.Prefix; + delete conf.Filter; + // OSSBucketDestination + conf.OSSBucketDestination = conf.Destination.OSSBucketDestination; + // OSSBucketDestination.rolename + conf.OSSBucketDestination.rolename = conf.OSSBucketDestination.RoleArn.replace(/.*\//, ''); + delete conf.OSSBucketDestination.RoleArn; + // OSSBucketDestination.bucket + conf.OSSBucketDestination.bucket = conf.OSSBucketDestination.Bucket.replace(/.*:::/, ''); + delete conf.OSSBucketDestination.Bucket; + delete conf.Destination; + // frequency + conf.frequency = conf.Schedule.Frequency; + delete conf.Schedule.Frequency; + // optionalFields + if (((_a = conf === null || conf === void 0 ? void 0 : conf.OptionalFields) === null || _a === void 0 ? void 0 : _a.Field) && !isArray_1.isArray((_b = conf.OptionalFields) === null || _b === void 0 ? void 0 : _b.Field)) + conf.OptionalFields.Field = [conf.OptionalFields.Field]; + }); + // firstLowerCase + _ = formatObjKey_1.formatObjKey(_, 'firstLowerCase', { exclude: ['OSSBucketDestination', 'SSE-OSS', 'SSE-KMS'] }); + return _; +} diff --git a/lib/common/utils/formatInventoryConfig.ts b/lib/common/utils/formatInventoryConfig.ts new file mode 100644 index 000000000..7b71c0b1d --- /dev/null +++ b/lib/common/utils/formatInventoryConfig.ts @@ -0,0 +1,40 @@ +import { dataFix } from '../utils/dataFix'; +import { isObject } from '../utils/isObject'; +import { isArray } from '../utils/isArray'; +import { formatObjKey } from '../utils/formatObjKey'; + +export function formatInventoryConfig(inventoryConfig, toArray = false) { + if (toArray && isObject(inventoryConfig)) inventoryConfig = [inventoryConfig]; + + if (isArray(inventoryConfig)) { + inventoryConfig = inventoryConfig.map(formatFn); + } else { + inventoryConfig = formatFn(inventoryConfig); + } + return inventoryConfig; +} + +function formatFn(_) { + dataFix(_, { bool: ['IsEnabled'] }, conf => { + // prefix + conf.prefix = conf.Filter.Prefix; + delete conf.Filter; + // OSSBucketDestination + conf.OSSBucketDestination = conf.Destination.OSSBucketDestination; + // OSSBucketDestination.rolename + conf.OSSBucketDestination.rolename = conf.OSSBucketDestination.RoleArn.replace(/.*\//, ''); + delete conf.OSSBucketDestination.RoleArn; + // OSSBucketDestination.bucket + conf.OSSBucketDestination.bucket = conf.OSSBucketDestination.Bucket.replace(/.*:::/, ''); + delete conf.OSSBucketDestination.Bucket; + delete conf.Destination; + // frequency + conf.frequency = conf.Schedule.Frequency; + delete conf.Schedule.Frequency; + // optionalFields + if (conf?.OptionalFields?.Field && !isArray(conf.OptionalFields?.Field)) conf.OptionalFields.Field = [conf.OptionalFields.Field]; + }); + // firstLowerCase + _ = formatObjKey(_, 'firstLowerCase', { exclude: ['OSSBucketDestination', 'SSE-OSS', 'SSE-KMS'] }); + return _; +} diff --git a/lib/common/utils/formatObjKey.d.ts b/lib/common/utils/formatObjKey.d.ts index ef765b9e4..4e370577e 100644 --- a/lib/common/utils/formatObjKey.d.ts +++ b/lib/common/utils/formatObjKey.d.ts @@ -1 +1,6 @@ -export declare function formatObjKey(obj: any, type: string): any; +interface Config { + exclude?: string[]; +} +declare type FormatObjKeyType = 'firstUpperCase' | 'firstLowerCase'; +export declare function formatObjKey(obj: any, type: FormatObjKeyType, options?: Config): any; +export {}; diff --git a/lib/common/utils/formatObjKey.js b/lib/common/utils/formatObjKey.js index 020292f81..f4287c4be 100644 --- a/lib/common/utils/formatObjKey.js +++ b/lib/common/utils/formatObjKey.js @@ -1,7 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatObjKey = void 0; -function formatObjKey(obj, type) { +function formatObjKey(obj, type, options) { if (obj === null || typeof obj !== 'object') { return obj; } @@ -9,19 +9,22 @@ function formatObjKey(obj, type) { if (Array.isArray(obj)) { o = []; for (let i = 0; i < obj.length; i++) { - o.push(formatObjKey(obj[i], type)); + o.push(formatObjKey(obj[i], type, options)); } } else { o = {}; Object.keys(obj).forEach((key) => { - o[handelFormat(key, type)] = formatObjKey(obj[key], type); + o[handelFormat(key, type, options)] = formatObjKey(obj[key], type, options); }); } return o; } exports.formatObjKey = formatObjKey; -function handelFormat(key, type) { +function handelFormat(key, type, options) { + var _a; + if (options && ((_a = options.exclude) === null || _a === void 0 ? void 0 : _a.includes(key))) + return key; if (type === 'firstUpperCase') { key = key.replace(/^./, (_) => _.toUpperCase()); } diff --git a/lib/common/utils/formatObjKey.ts b/lib/common/utils/formatObjKey.ts index 6d43b570c..ac3db57a9 100644 --- a/lib/common/utils/formatObjKey.ts +++ b/lib/common/utils/formatObjKey.ts @@ -1,4 +1,11 @@ -export function formatObjKey(obj: any, type: string) { + +interface Config { + exclude?: string[]; +} + +type FormatObjKeyType = 'firstUpperCase' | 'firstLowerCase'; + +export function formatObjKey(obj: any, type: FormatObjKeyType, options?: Config) { if (obj === null || typeof obj !== 'object') { return obj; } @@ -7,18 +14,19 @@ export function formatObjKey(obj: any, type: string) { if (Array.isArray(obj)) { o = []; for (let i = 0; i < obj.length; i++) { - o.push(formatObjKey(obj[i], type)); + o.push(formatObjKey(obj[i], type, options)); } } else { o = {}; Object.keys(obj).forEach((key) => { - o[handelFormat(key, type)] = formatObjKey(obj[key], type); + o[handelFormat(key, type, options)] = formatObjKey(obj[key], type, options); }); } return o; } -function handelFormat(key: string, type: string) { +function handelFormat(key: string, type: FormatObjKeyType, options?: Config) { + if (options && options.exclude?.includes(key)) return key; if (type === 'firstUpperCase') { key = key.replace(/^./, (_: string) => _.toUpperCase()); } else if (type === 'firstLowerCase') { diff --git a/test/node/bucket.test.js b/test/node/bucket.test.js index 64c1d34a8..b9f561cd3 100644 --- a/test/node/bucket.test.js +++ b/test/node/bucket.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-loop-func */ +/* eslint-disable no-await-in-loop */ const assert = require('assert'); const utils = require('./utils'); @@ -376,7 +378,7 @@ describe('test/bucket.test.js', () => { const result1 = await store.putBucketWebsite(bucket, website); assert.strictEqual(result1.res.status, 200); const rules1 = await store.getBucketWebsite(bucket); - includesConf(rules1.routingRules, routingRules); + assert(includesConf(rules1.routingRules, routingRules)); assert.strictEqual(rules1.supportSubDir, website.supportSubDir); assert.strictEqual(rules1.type, website.type); @@ -384,7 +386,7 @@ describe('test/bucket.test.js', () => { const result2 = await store.putBucketWebsite(bucket, website); assert.strictEqual(result2.res.status, 200); const rules2 = await store.getBucketWebsite(bucket); - includesConf(rules2.routingRules, website.routingRules); + assert(includesConf(rules2.routingRules, website.routingRules)); }); it('should throw error when RoutingRules is not Array', async () => { @@ -1227,4 +1229,160 @@ describe('test/bucket.test.js', () => { } }); }); + describe('inventory()', () => { + const inventory = { + id: 'default', + isEnabled: false, + prefix: 'ttt', + OSSBucketDestination: { + format: 'CSV', + accountId: '1817184078010220', + rolename: 'AliyunOSSRole', + bucket, + prefix: 'test', + }, + frequency: 'Daily', + includedObjectVersions: 'All', + optionalFields: { + field: ['Size', 'LastModifiedDate'], + }, + }; + + describe('putBucketInventory', () => { + before(() => { + inventory.OSSBucketDestination.bucket = bucket; + }); + it('should put bucket inventory', async () => { + try { + await store.putBucketInventory(bucket, inventory); + } catch (err) { + assert(false, err); + } + }); + it('should return inventory array when inventory is one config', async () => { + const inventoryRes = await store.listBucketInventory(bucket); + assert(Array.isArray(inventoryRes.inventoryList)); + assert(inventoryRes.inventoryList.length === 1); + assert.strictEqual(inventoryRes.status, 200); + }); + it('should put bucket inventory when no optionalFields or no field', async () => { + try { + inventory.id = 'test_optionalFields'; + delete inventory.optionalFields; + await store.putBucketInventory(bucket, inventory); + + inventory.id = 'test_field'; + inventory.optionalFields = {}; + await store.putBucketInventory(bucket, inventory); + + inventory.id = 'test_field_is_one'; + inventory.optionalFields = { + field: ['Size'], + }; + await store.putBucketInventory(bucket, inventory); + assert(true); + } catch (err) { + assert(false, err); + } + }); + it('should put bucket inventory when no prefix', async () => { + try { + inventory.id = 'test_prefix'; + delete inventory.prefix; + await store.putBucketInventory(bucket, inventory); + assert(true); + } catch (err) { + assert(false, err); + } + }); + it('should put bucket inventory when no OSSBucketDestination prefix', async () => { + try { + inventory.id = 'test_OSSBucketDestination_prefix'; + delete inventory.OSSBucketDestination.prefix; + await store.putBucketInventory(bucket, inventory); + assert(true); + } catch (err) { + assert(false, err); + } + }); + it('should put bucket inventory when has encryption', async () => { + try { + inventory.id = 'test_encryption_SSE-OSS'; + inventory.OSSBucketDestination.encryption = { 'SSE-OSS': '' }; + await store.putBucketInventory(bucket, inventory); + assert(true); + } catch (err) { + assert(false, err); + } + }); + }); + describe('getBucketInventory', () => { + let testGetInventory; + it('should get bucket inventory by inventoryId', async () => { + try { + const result = await store.getBucketInventory(bucket, inventory.id); + testGetInventory = result.inventory; + assert(includesConf(testGetInventory, inventory)); + } catch (err) { + assert(false); + } + }); + it('should return Field array when Field value is one length Array', async () => { + try { + assert( + testGetInventory.optionalFields && + testGetInventory.optionalFields.field && + Array.isArray(testGetInventory.optionalFields.field) && + testGetInventory.optionalFields.field.length === 1 + ); + } catch (err) { + assert(false); + } + }); + }); + describe('listBucketInventory', () => { + before(async () => { + let _index = 0; + async function putInventoryList() { + await Promise.all( + new Array(1).fill(1).map(() => { + _index++; + return store.putBucketInventory(bucket, Object.assign({}, inventory, { id: `test_list_${_index}` })); + }) + ); + } + + await putInventoryList(); + }); + it('should list bucket inventory', async () => { + const inventoryRes = await store.listBucketInventory(bucket); + assert.strictEqual(inventoryRes.status, 200); + }); + }); + describe('deleteBucketInventory', () => { + it('should delete bukcet inventory', async () => { + let inventoryList = []; + let isTruncated; + let continuationToken; + do { + const inventoryRes = await store.listBucketInventory(bucket, { continuationToken }); + inventoryList = [...inventoryList, ...inventoryRes.inventoryList]; + isTruncated = inventoryRes.isTruncated; + continuationToken = inventoryRes.nextContinuationToken; + } while (isTruncated); + try { + // avoid Qps limit + do { + const list = inventoryList.splice(0, 10); + await Promise.all(list.map(_ => store.deleteBucketInventory(bucket, _.id))); + utils.sleep(400); + } while (inventoryList.length); + assert(true); + } catch (err) { + assert(false, err); + } + }); + }); + }); + }); diff --git a/test/node/dataFix.test.js b/test/node/dataFix.test.js new file mode 100644 index 000000000..c4aa68882 --- /dev/null +++ b/test/node/dataFix.test.js @@ -0,0 +1,225 @@ +const assert = require('assert'); +const { dataFix } = require('../../lib/common/utils/dataFix'); +const { sleep } = require('./utils'); + +describe('dataFix()', () => { + before(async () => { + await sleep(1000); + }); + describe('data is not object', () => { + it('should return without handle', () => { + const data = 'string'; + + const conf = { + remove: ['rm', 'rm2'], + }; + dataFix(data, conf); + }); + }); + + describe('remove : array - remove unwanted props', () => { + it('should remove what is not needed', () => { + const data = { + rmNot: 'do NOT remove me', + rm: [], + rm2: 'what ever value dos NOT matter', + }; + + const conf = { + remove: ['rm', 'rm2'], + }; + + dataFix(data, conf); + + assert(!conf.remove.find(_ => Object.prototype.hasOwnProperty.call(data, _))); + assert(Object.prototype.hasOwnProperty.call(data, 'rmNot')); + }); + }); + + describe('lowerFirst : boolean - turn key into first-letter-lower-case', () => { + const One = 'One'; + const Another = 'Another'; + const Both = 'Both'; + const both = 'both'; + const data = { + One, + Another, + Both, + both, + }; + + dataFix(data, { + lowerFirst: true, + }); + + it('should covert and remove the Old', () => { + assert(!data.One); + assert(!data.Another); + assert(data.one); + assert(data.another); + }); + + it('should not covert if lower-case will replace existed', () => { + assert.strictEqual(Both, data.Both); + assert.strictEqual(both, data.both); + }); + }); + + describe('bool : array - turn values into boolean if can be converted', () => { + const cannotConvertNumber2 = 2; + const cannotConvertOtherString = 'cannot convert'; + const data = { + trueB: true, + trueL: 'true', + trueU: 'TRUE', + true1: '1', + true1N: 1, + falseB: false, + falseL: 'false', + falseU: 'FALSE', + false0: '0', + false0N: 0, + falseNull: null, + cannotConvertNumber2, + cannotConvertOtherString, + }; + + dataFix(data, { + bool: [ + 'trueB', + 'trueL', + 'trueU', + 'true1', + 'true1N', + 'falseB', + 'falseL', + 'falseU', + 'false0', + 'false0N', + 'falseNull', + 'cannotConvertNumber2', + 'cannotConvertOtherString', + 'nonExist', + ], + }); + + it('should boolean true/false remain boolean', () => { + assert.strictEqual(data.trueB, true); + assert.strictEqual(data.falseB, false); + }); + + it('should convert true TURE 1 (number or string) to boolean true', () => { + assert.strictEqual(data.trueL, true); + assert.strictEqual(data.trueU, true); + assert.strictEqual(data.true1, true); + assert.strictEqual(data.true1N, true); + }); + + it('should convert false FALSE 0 (number or string) to boolean false', () => { + assert.strictEqual(data.falseL, false); + assert.strictEqual(data.falseU, false); + assert.strictEqual(data.false0, false); + assert.strictEqual(data.false0N, false); + }); + + it('should convert null / undefined to false', () => { + assert.strictEqual(data.falseNull, false); + assert.strictEqual(data.nonExist, false); + }); + + it('should leave those cannot be converted as is', () => { + assert.strictEqual(cannotConvertNumber2, data.cannotConvertNumber2); + assert.strictEqual( + cannotConvertOtherString, + data.cannotConvertOtherString + ); + }); + }); + + describe('rename : object - rename bad prop keys into better names', () => { + const existValue = 123456; + const renameToAlready = 'rename to already'; + const alreadyExist = 'already'; + const data = { + existValue, + renameToAlready, + alreadyExist, + }; + + dataFix(data, { + rename: { + existValue: 'existValueRenamed', + nonExistValue: 'nonExistValueRenamed', + renameToAlready: 'alreadyExist', + }, + }); + + it('should replace existed values with new name and same value', () => { + assert(!data.existValue); + assert.strictEqual(data.existValueRenamed, existValue); + }); + + it('should not add prop when the prop-to-be-renamed does NOT exist', () => { + assert(!data.nonExistValueRenamed); + assert(!data.nonExistValue); + }); + + it('should not rename if a name already exist', () => { + assert.strictEqual(data.alreadyExist, alreadyExist); + assert.strictEqual(data.renameToAlready, renameToAlready); + }); + }); + + describe('camel : array - turn key into camel string', () => { + const Both = 'Both'; + const both = 'bothBoth'; + const data = { + One: 'One', + 'Another-another': 'Another-another', + 'Both-both': Both, + bothBoth: both, + }; + + dataFix(data, { + camel: [...Object.keys(data), 'noExistkey'], + }); + + it('should covert and remove the Old', () => { + assert(data.one); + assert(data.anotherAnother); + }); + + it('should not covert if camel will replace existed', () => { + assert.strictEqual(Both, data['Both-both']); + assert.strictEqual(both, data.bothBoth); + }); + + it('should not covert if camel origin key is not exist', () => { + // eslint-disable-next-line no-prototype-builtins + assert(!data.hasOwnProperty('NoExistkey')); + }); + }); + + describe('finalKill: function', () => { + it('should correct fix data', () => { + const data = { + test: 1, + test1: 2, + needDelete: 'del', + needDelete1: 'del', + }; + + const delKey = 'needDelete'; + const addKey = 'addKey'; + dataFix(data, {}, (o) => { + Object.keys(o).forEach(_ => { + if (_.includes(delKey)) delete o[_]; + }); + o[addKey] = addKey; + }); + + assert(!Object.keys(data).find(_ => _.includes(delKey))); + assert.strictEqual(data.addKey, addKey); + }); + }); +});