diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 9bcdb4688..000000000 --- a/.eslintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": [ - "eslint-config-egg/typescript", - "eslint-config-egg/lib/rules/enforce-node-prefix" - ] -} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d4b704ba8..ccb74cb94 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -18,31 +18,31 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18, 20, 22, 23] + node-version: [18, 20, 22, 24] os: [ubuntu-latest] steps: - - name: Checkout Git Source - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - - name: Install Dependencies - run: npm i - - - name: Continuous Integration - run: npm run ci - env: - OSS_CLIENT_ID: ${{ secrets.OSS_CLIENT_ID }} - OSS_CLIENT_SECRET: ${{ secrets.OSS_CLIENT_SECRET }} - OSS_CLIENT_REGION: ${{ secrets.OSS_CLIENT_REGION }} - OSS_CLIENT_ENDPOINT: ${{ secrets.OSS_CLIENT_ENDPOINT }} - OSS_CLIENT_BUCKET: ${{ secrets.OSS_CLIENT_BUCKET }} - - - name: Code Coverage - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} + - name: Checkout Git Source + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: npm i + + - name: Continuous Integration + run: npm run ci + env: + OSS_CLIENT_ID: ${{ secrets.OSS_CLIENT_ID }} + OSS_CLIENT_SECRET: ${{ secrets.OSS_CLIENT_SECRET }} + OSS_CLIENT_REGION: ${{ secrets.OSS_CLIENT_REGION }} + OSS_CLIENT_ENDPOINT: ${{ secrets.OSS_CLIENT_ENDPOINT }} + OSS_CLIENT_BUCKET: ${{ secrets.OSS_CLIENT_BUCKET }} + + - name: Code Coverage + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c6cbb18f..43b8276b0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release on: push: - branches: [ master ] + branches: [master] jobs: release: diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..2312dc587 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 000000000..1dbb0ab27 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,143 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "env": { + "node": true, + "mocha": true + }, + "categories": { + "correctness": "error", + "perf": "error", + "nursery": "error", + "restriction": "error", + "style": "error", + "pedantic": "error", + "suspicious": "error" + }, + "plugins": [ + "import", + "typescript", + "unicorn", + "jsdoc", + "node", + "promise", + "oxc" + ], + "rules": { + // eslint + "constructor-super": "error", + "getter-return": "error", + "no-undef": "error", + "no-unreachable": "error", + "no-var": "error", + "no-eq-null": "error", + "no-await-in-loop": "allow", + "eqeqeq": ["error", "smart"], + "init-declarations": "allow", + "curly": "allow", + "no-ternary": "allow", + "max-params": ["error", 5], + "no-await-expression-member": "error", + "no-continue": "allow", + "guard-for-in": "allow", + "func-style": "allow", + "sort-imports": "allow", + "yoda": "allow", + "sort-keys": "allow", + "no-magic-numbers": "allow", + "no-duplicate-imports": "error", + "no-multi-assign": "error", + "func-names": "error", + "default-param-last": "error", + "prefer-object-spread": "error", + "no-undefined": "allow", + "no-plusplus": "allow", + // maybe warn + "no-console": "warn", + "no-extraneous-class": "allow", + "no-empty-function": "allow", + "max-depth": ["error", 6], + "max-lines-per-function": "allow", + "no-lonely-if": "error", + "max-lines": "allow", + "require-await": "allow", + "max-nested-callbacks": ["error", 5], + "max-classes-per-file": "allow", + "radix": "allow", + "no-negated-condition": "error", + "no-else-return": "error", + "no-throw-literal": "error", + + // import + "import/exports-last": "allow", + "import/max-dependencies": "allow", + "import/no-cycle": "error", + "import/no-anonymous-default-export": "allow", + "import/no-namespace": "error", + "import/named": "error", + "import/export": "error", + "import/no-default-export": "allow", + "import/unambiguous": "error", + "import/group-exports": "allow", + + // promise + "promise/no-return-wrap": "error", + "promise/param-names": "error", + "promise/prefer-await-to-callbacks": "error", + "promise/prefer-await-to-then": "error", + "promise/prefer-catch": "error", + "promise/no-return-in-finally": "error", + "promise/avoid-new": "error", + + // unicorn + "unicorn/error-message": "error", + "unicorn/no-null": "allow", + "unicorn/filename-case": "allow", + "unicorn/prefer-structured-clone": "error", + "unicorn/prefer-logical-operator-over-ternary": "error", + "unicorn/prefer-number-properties": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-string-slice": "error", + // "unicorn/no-null": "error", + "unicorn/throw-new-error": "error", + "unicorn/catch-error-name": "allow", + "unicorn/prefer-spread": "allow", + "unicorn/numeric-separators-style": "error", + "unicorn/prefer-string-raw": "error", + "unicorn/text-encoding-identifier-case": "error", + "unicorn/no-array-for-each": "error", + "unicorn/explicit-length-check": "error", + "unicorn/no-lonely-if": "error", + "unicorn/no-useless-undefined": "allow", + "unicorn/prefer-date-now": "error", + "unicorn/no-static-only-class": "allow", + "unicorn/no-typeof-undefined": "error", + "unicorn/prefer-negative-index": "error", + "unicorn/no-anonymous-default-export": "allow", + + // oxc + "oxc/no-map-spread": "error", + "oxc/no-rest-spread-properties": "allow", + "oxc/no-optional-chaining": "allow", + "oxc/no-async-await": "allow", + + // typescript + "typescript/explicit-function-return-type": "allow", + "typescript/consistent-type-imports": "error", + "typescript/consistent-type-definitions": "error", + "typescript/consistent-indexed-object-style": "allow", + "typescript/no-inferrable-types": "error", + "typescript/array-type": "error", + "typescript/no-non-null-assertion": "error", + "typescript/no-explicit-any": "error", + "typescript/no-import-type-side-effects": "error", + "typescript/no-dynamic-delete": "error", + "typescript/prefer-ts-expect-error": "error", + "typescript/ban-ts-comment": "error", + "typescript/prefer-enum-initializers": "error", + + // jsdoc + "jsdoc/require-returns": "allow", + "jsdoc/require-param": "allow" + }, + "ignorePatterns": ["index.d.ts", "test/fixtures/**", "__snapshots__"] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..9ffa84db8 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +CHANGELOG.md +__snapshots__ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..43aee1a49 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "arrowParens": "avoid" +} diff --git a/README.md b/README.md index cdde01791..b5272be51 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ const ossObject = new OSSObject({ endpoint: '', accessKeyId: '', accessKeySecret: '', - bucket: '' + bucket: '', }); ``` @@ -112,7 +112,7 @@ const ossObject = new OSSObject({ endpoint: '', accessKeyId: '', accessKeySecret: '', - bucket: '' + bucket: '', }); ``` @@ -208,21 +208,23 @@ parameters: - name {String} object name store on OSS - file {String|Buffer|ReadStream} object local path, content buffer or ReadStream content instance use in Node, Blob and html5 File - [options] {Object} optional parameters + - [timeout] {Number} the operation timeout - [mime] {String} custom mime, will send with `Content-Type` entity header - [meta] {Object} user meta, will send with `x-oss-meta-` prefix string e.g.: `{ uid: 123, pid: 110 }` - [callback] {Object} The callback parameter is composed of a JSON string encoded in Base64,detail [see](https://www.alibabacloud.com/help/doc-detail/31989.htm)
+ - url {String} After a file is uploaded successfully, the OSS sends a callback request to this URL. - [host] {String} The host header value for initiating callback requests. - body {String} The value of the request body when a callback is initiated, for example, `key=${key}&etag=${etag}&my_var=${x:my_var}`. - [contentType] {String} The Content-Type of the callback requests initiatiated, It supports application/x-www-form-urlencoded and application/json, and the former is the default value. - [customValue] {Object} Custom parameters are a map of key-values
- e.g.: + e.g.: - ```js - var customValue = {var1: 'value1', var2: 'value2'} - ``` + ```js + var customValue = { var1: 'value1', var2: 'value2' }; + ``` - [headers] {Object} extra headers - 'Cache-Control' cache control for download, e.g.: `Cache-Control: public, no-cache` @@ -333,22 +335,24 @@ parameters: - name {String} object name store on OSS - stream {ReadStream} object ReadStream content instance - [options] {Object} optional parameters + - [contentLength] {Number} the stream length, `chunked encoding` will be used if absent - [timeout] {Number} the operation timeout - [mime] {String} custom mime, will send with `Content-Type` entity header - [meta] {Object} user meta, will send with `x-oss-meta-` prefix string e.g.: `{ uid: 123, pid: 110 }` - [callback] {Object} The callback parameter is composed of a JSON string encoded in Base64,detail [see](https://www.alibabacloud.com/help/doc-detail/31989.htm)
+ - url {String} After a file is uploaded successfully, the OSS sends a callback request to this URL. - [host] {String} The host header value for initiating callback requests. - body {String} The value of the request body when a callback is initiated, for example, key=${key}&etag=${etag}&my_var=${x:my_var}. - [contentType] {String} The Content-Type of the callback requests initiatiated, It supports application/x-www-form-urlencoded and application/json, and the former is the default value. - [customValue] {Object} Custom parameters are a map of key-values
- e.g.: + e.g.: - ```js - var customValue = {var1: 'value1', var2: 'value2'} - ``` + ```js + var customValue = { var1: 'value1', var2: 'value2' }; + ``` - [headers] {Object} extra headers, detail see [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html) - 'Cache-Control' cache control for download, e.g.: `Cache-Control: public, no-cache` @@ -450,7 +454,10 @@ e.g.: const url = store.generateObjectUrl('foo/bar.jpg'); // cdnUrl should be `https://${bucketname}.${endpotint}foo/bar.jpg` -const cdnUrl = store.generateObjectUrl('foo/bar.jpg', 'https://mycdn.domian.com'); +const cdnUrl = store.generateObjectUrl( + 'foo/bar.jpg', + 'https://mycdn.domian.com' +); // cdnUrl should be `https://mycdn.domian.com/foo/bar.jpg` ``` @@ -466,13 +473,13 @@ parameters: - [versionId] {String} the version id of history object - [headers] {Object} extra headers, detail see [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html) - 'If-Modified-Since' object modified after this time will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified - 'If-Unmodified-Since' object modified before this time will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-Match' object etag equal this will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-None-Match' object etag not equal this will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified Success will return the object's meta information. @@ -480,7 +487,7 @@ object: - status {Number} response status, maybe 200 or 304 - meta {Object} object user meta, if not set on `put()`, will return null. - If return status 304, meta will be null too + If return status 304, meta will be null too - res {Object} response info, including - status {Number} response status - headers {Object} response headers @@ -521,7 +528,7 @@ const object = await this.store.head('ossdemo/head-meta'); ### .getObjectMeta(name[, options]) -Get an object meta info include ETag、Size、LastModified and so on, not return object content. +Get an object meta info include ETag、Size、LastModified and so on, not return object content. parameters: @@ -570,13 +577,13 @@ parameters: - [headers] {Object} extra headers, detail see [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616.html) - 'Range' get specifying range bytes content, e.g.: `Range: bytes=0-9` - 'If-Modified-Since' object modified after this time will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified - 'If-Unmodified-Since' object modified before this time will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-Match' object etag equal this will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-None-Match' object etag not equal this will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified Success will return the info contains response. @@ -600,7 +607,7 @@ const filepath = '/home/ossdemo/demo.txt'; await store.get('ossdemo/demo.txt', filepath); ``` -_ Store object to a writestream +\_ Store object to a writestream ```js await store.get('ossdemo/demo.txt', somestream); @@ -617,7 +624,9 @@ console.log(Buffer.isBuffer(result.content)); ```js const filepath = '/home/ossdemo/demo.png'; -await store.get('ossdemo/demo.png', filepath, {process: 'image/resize,w_200'}); +await store.get('ossdemo/demo.png', filepath, { + process: 'image/resize,w_200', +}); ``` - Get a not exists object @@ -634,7 +643,7 @@ await store.get('ossdemo/not-exists-demo.txt', filepath); const filepath = '/home/ossdemo/demo.txt'; const versionId = 'versionId string'; await store.get('ossdemo/not-exists-demo.txt', filepath, { - versionId + versionId, }); ``` @@ -650,20 +659,20 @@ parameters: - [process] {String} image process params, will send with `x-oss-process` - [headers] {Object} extra headers - 'If-Modified-Since' object modified after this time will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified - 'If-Unmodified-Since' object modified before this time will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-Match' object etag equal this will return 200 and object meta, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-None-Match' object etag not equal this will return 200 and object meta, - otherwise return 304 not modified + otherwise return 304 not modified Success will return the stream instance and response info. object: - stream {ReadStream} readable stream instance - if response status is not 200, stream will be `null`. + if response status is not 200, stream will be `null`. - res {Object} response info, including - status {Number} response status - headers {Object} response headers @@ -746,9 +755,9 @@ parameters: - 'If-None-Match' do copy if source object etag not equal this, otherwise throw PreconditionFailedError - 'If-Modified-Since' do copy if source object modified after this time, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - 'If-Unmodified-Since' do copy if source object modified before this time, - otherwise throw PreconditionFailedError + otherwise throw PreconditionFailedError - See more: [CopyObject](https://help.aliyun.com/document_detail/31979.html?#title-tzy-vxc-ncx) Success will return the copy result in `data` property. @@ -771,7 +780,7 @@ example: - Copy same bucket object ```js -store.copy('newName', 'oldName').then((result) => { +store.copy('newName', 'oldName').then(result => { console.log(result); }); ``` @@ -779,7 +788,7 @@ store.copy('newName', 'oldName').then((result) => { - Copy other bucket object ```js -store.copy('logo.png', 'logo.png', 'other-bucket').then((result) => { +store.copy('logo.png', 'logo.png', 'other-bucket').then(result => { console.log(result); }); ``` @@ -787,10 +796,12 @@ store.copy('logo.png', 'logo.png', 'other-bucket').then((result) => { - Copy historic object ```js -const versionId = 'your verisonId' -store.copy('logo.png', 'logo.png', 'other-bucket', { versionId }).then((result) => { - console.log(result); -}); +const versionId = 'your verisonId'; +store + .copy('logo.png', 'logo.png', 'other-bucket', { versionId }) + .then(result => { + console.log(result); + }); ``` ### .putMeta(name, meta[, options]) @@ -825,7 +836,8 @@ example: ```js const result = await store.putMeta('ossdemo.txt', { - uid: 1, pid: 'p123' + uid: 1, + pid: 'p123', }); console.log(result); ``` @@ -848,7 +860,7 @@ parameters: - [options] {Object} optional parameters - [quiet] {Boolean} quiet mode or verbose mode, default is `false`, verbose mode quiet mode: if all objects delete succes, return emtpy response. - otherwise return delete error object results. + otherwise return delete error object results. verbose mode: return all object delete results. - [timeout] {Number} the operation timeout @@ -871,7 +883,7 @@ example: ```js const result = await store.deleteMulti(['obj1', 'obj2', 'obj3'], { - quiet: true + quiet: true, }); ``` @@ -886,12 +898,12 @@ const result = await store.deleteMulti(['obj1', 'obj2', 'obj3']); ```js const obj1 = { key: 'key1', - versionId: 'versionId1' -} + versionId: 'versionId1', +}; const obj2 = { key: 'key2', - versionId: 'versionId2' -} + versionId: 'versionId2', +}; const result = await store.deleteMulti([obj1, obj2]); ``` @@ -943,7 +955,7 @@ console.log(result.objects); ```js const result = await store.list({ - prefix: 'fun/' + prefix: 'fun/', }); console.log(result.objects); ``` @@ -953,7 +965,7 @@ console.log(result.objects); ```js const result = await store.list({ prefix: 'fun/', - delimiter: '/' + delimiter: '/', }); console.log(result.objects); ``` @@ -992,6 +1004,7 @@ Success will return objects list on `objects` properties. - nextContinuationToken {String} next continuation-token string - keyCount {Number} The number of keys returned for this request. If Delimiter is specified, KeyCount is the sum of the elements in Key and CommonPrefixes. - res {Object} response info, including + - status {Number} response status - headers {Object} response headers - size {Number} response size @@ -1001,7 +1014,7 @@ Success will return objects list on `objects` properties. ```js const result = await store.listV2({ - 'max-keys': 10 + 'max-keys': 10, }); console.log(result.objects); ``` @@ -1010,7 +1023,7 @@ console.log(result.objects); ```js const result = await store.listV2({ - prefix: 'fun/' + prefix: 'fun/', }); console.log(result.objects); ``` @@ -1020,7 +1033,7 @@ console.log(result.objects); ```js const result = await store.listV2({ prefix: 'fun/', - delimiter: '/' + delimiter: '/', }); console.log(result.objects); ``` @@ -1031,7 +1044,7 @@ console.log(result.objects); const result = await store.listV2({ delimiter: '/', prefix: 'a/', - 'start-after': 'a/b' + 'start-after': 'a/b', }); console.log(result.objects); ``` @@ -1094,7 +1107,7 @@ console.log(result.deleteMarker); ```js const result = await store.getBucketVersions({ - 'keyMarker': 'keyMarker' + keyMarker: 'keyMarker', }); console.log(result.objects); ``` @@ -1103,8 +1116,8 @@ console.log(result.objects); ```js const result = await store.getBucketVersions({ - 'versionIdMarker': 'versionIdMarker', - 'keyMarker': 'keyMarker' + versionIdMarker: 'versionIdMarker', + keyMarker: 'keyMarker', }); console.log(result.objects); console.log(result.deleteMarker); @@ -1149,7 +1162,7 @@ console.log(url); // -------------------------------------------------- const url = store.signatureUrl('ossdemo.txt', { expires: 3600, - method: 'PUT' + method: 'PUT', }); console.log(url); @@ -1168,8 +1181,8 @@ const url = store.signatureUrl('ossdemo.txt', { expires: 3600, response: { 'content-type': 'text/custom', - 'content-disposition': 'attachment' - } + 'content-disposition': 'attachment', + }, }); console.log(url); @@ -1180,13 +1193,13 @@ console.log(url); ```js const url = store.signatureUrl('ossdemo.png', { - process: 'image/resize,w_200' + process: 'image/resize,w_200', }); console.log(url); // -------------------------------------------------- const url = store.signatureUrl('ossdemo.png', { expires: 3600, - process: 'image/resize,w_200' + process: 'image/resize,w_200', }); console.log(url); ``` @@ -1230,7 +1243,7 @@ console.log(url); // -------------------------------------------------- const url = await store.asyncSignatureUrl('ossdemo.txt', { expires: 3600, - method: 'PUT' + method: 'PUT', }); console.log(url); // put object with signatureUrl @@ -1246,8 +1259,8 @@ const url = await store.asyncSignatureUrl('ossdemo.txt', { expires: 3600, response: { 'content-type': 'text/custom', - 'content-disposition': 'attachment' - } + 'content-disposition': 'attachment', + }, }); console.log(url); // put operation @@ -1257,13 +1270,13 @@ console.log(url); ```js const url = await store.asyncSignatureUrl('ossdemo.png', { - process: 'image/resize,w_200' + process: 'image/resize,w_200', }); console.log(url); // -------------------------------------------------- const url = await store.asyncSignatureUrl('ossdemo.png', { expires: 3600, - process: 'image/resize,w_200' + process: 'image/resize,w_200', }); console.log(url); ``` @@ -1299,9 +1312,9 @@ await store.putACL('ossdemo.txt', 'public-read'); - Set an history object's ACL ```js -const versionId = 'object versionId' +const versionId = 'object versionId'; await store.putACL('ossdemo.txt', 'public-read', { - versionId + versionId, }); ``` @@ -1337,7 +1350,7 @@ console.log(result.acl); - Get an history object's ACL ```js -const versionId = 'object versionId' +const versionId = 'object versionId'; const result = await store.getACL('ossdemo.txt', { versionId }); console.log(result.acl); ``` @@ -1374,14 +1387,17 @@ console.log(result.status); - Restore an object with ColdArchive type ```js -const result = await store.restore('ossdemo.txt',{type:'ColdArchive'}); +const result = await store.restore('ossdemo.txt', { type: 'ColdArchive' }); console.log(result.status); ``` - Days for unfreezing Specifies the days for unfreezing ```js -const result = await store.restore('ossdemo.txt',{type:'ColdArchive',Days:2}); +const result = await store.restore('ossdemo.txt', { + type: 'ColdArchive', + Days: 2, +}); console.log(result.status); ``` @@ -1402,6 +1418,7 @@ parameters: - name {String} object name - targetName {String} target object name - [options] {Object} optional parameters + - [storageClass] {String} the storage type include (Standard,IA,Archive) - [meta] {Object} user meta, will send with `x-oss-meta-` prefix string - [headers] {Object} extra headers, detail see [PutSymlink](https://help.aliyun.com/document_detail/45126.html#title-x71-l2b-7i8) @@ -1419,11 +1436,11 @@ const options = { storageClass: 'IA', meta: { uid: '1', - slus: 'test.html' - } -} -const result = await store.putSymlink('ossdemo.txt', 'targetName', options) -console.log(result.res) + slus: 'test.html', + }, +}; +const result = await store.putSymlink('ossdemo.txt', 'targetName', options); +console.log(result.res); ``` putSymlink multiversion @@ -1433,11 +1450,11 @@ const options = { storageClass: 'IA', meta: { uid: '1', - slus: 'test.html' + slus: 'test.html', }, -} -const result = await store.putSymlink('ossdemo.txt', 'targetName', options) -console.log(result.res.headers['x-oss-version-id']) +}; +const result = await store.putSymlink('ossdemo.txt', 'targetName', options); +console.log(result.res.headers['x-oss-version-id']); ``` ### .getSymlink(name[, options]) @@ -1462,16 +1479,16 @@ Success will return example: ```js -const result = await store.getSymlink('ossdemo.txt') -console.log(result.targetName) +const result = await store.getSymlink('ossdemo.txt'); +console.log(result.targetName); ``` for history object ```js const versionId = 'object versionId'; -const result = await store.getSymlink('ossdemo.txt', { versionId }) -console.log(result.targetName) +const result = await store.getSymlink('ossdemo.txt', { versionId }); +console.log(result.targetName); ``` ### .calculatePostSignature(policy) @@ -1562,9 +1579,10 @@ object: - res {Object} response info ```js -const sourceObject = 'a.png' -const targetObject = 'b.png' -const process = 'image/watermark,text_aGVsbG8g5Zu+54mH5pyN5Yqh77yB,color_ff6a00' +const sourceObject = 'a.png'; +const targetObject = 'b.png'; +const process = + 'image/watermark,text_aGVsbG8g5Zu+54mH5pyN5Yqh77yB,color_ff6a00'; await this.store.processObjectSave(sourceObject, targetObject, process); ``` @@ -1576,61 +1594,61 @@ Each error return by OSS server will contains these properties: - name {String} error name - message {String} error message - requestId {String} uuid for this request, if you meet some unhandled problem, - you can send this request id to OSS engineer to find out what's happend. + you can send this request id to OSS engineer to find out what's happend. - hostId {String} OSS cluster name for this request The following table lists the OSS error codes: [More code info](https://help.aliyun.com/knowledge_detail/32005.html) -code | status | message | message in Chinese ---- | --- | --- | --- -AccessDenied | 403 | Access Denied | 拒绝访问 -BucketAlreadyExists | 409 | Bucket already exists | Bucket 已经存在 -BucketNotEmpty | 409 | Bucket is not empty | Bucket 不为空 -RestoreAlreadyInProgress | 409 | The restore operation is in progress. | restore 操作正在进行中 -OperationNotSupported | 400 | The operation is not supported for this resource | 该资源暂不支持restore操作 -EntityTooLarge | 400 | Entity too large | 实体过大 -EntityTooSmall | 400 | Entity too small | 实体过小 -FileGroupTooLarge | 400 | File group too large | 文件组过大 -InvalidLinkName | 400 | Link name can't be the same as the object name | Object Link 与指向的 Object 同名 -LinkPartNotExist | 400 | Can't link to not exists object | Object Link 中指向的 Object 不存在 -ObjectLinkTooLarge | 400 | Too many links to this object | Object Link 中 Object 个数过多 -FieldItemTooLong | 400 | Post form fields items too large | Post 请求中表单域过大 -FilePartInterity | 400 | File part has changed | 文件 Part 已改变 -FilePartNotExist | 400 | File part not exists | 文件 Part 不存在 -FilePartStale| 400 | File part stale | 文件 Part 过时 -IncorrectNumberOfFilesInPOSTRequest | 400 | Post request contains invalid number of files | Post 请求中文件个数非法 -InvalidArgument | 400 | Invalid format argument | 参数格式错误 -InvalidAccessKeyId | 400 | Access key id not exists | Access Key ID 不存在 -InvalidBucketName | 400 | Invalid bucket name | 无效的 Bucket 名字 -InvalidDigest | 400 | Invalid digest | 无效的摘要 -InvalidEncryptionAlgorithm | 400 | Invalid encryption algorithm | 指定的熵编码加密算法错误 -InvalidObjectName | 400 | Invalid object name | 无效的 Object 名字 -InvalidPart | 400 | Invalid part | 无效的 Part -InvalidPartOrder | 400 | Invalid part order | 无效的 part 顺序 -InvalidPolicyDocument | 400 | Invalid policy document | 无效的 Policy 文档 -InvalidTargetBucketForLogging | 400 | Invalid bucket on logging operation | Logging 操作中有无效的目标 bucket -Internal | 500 | OSS server internal error | OSS 内部发生错误 -MalformedXML | 400 | Malformed XML format | XML 格式非法 -MalformedPOSTRequest | 400 | Invalid post body format | Post 请求的 body 格式非法 -MaxPOSTPreDataLengthExceeded | 400 | Post extra data too large | Post 请求上传文件内容之外的 body 过大 -MethodNotAllowed | 405 | Not allowed method | 不支持的方法 -MissingArgument | 411 | Missing argument | 缺少参数 -MissingContentLength | 411 | Missing `Content-Length` header | 缺少内容长度 -NoSuchBucket | 404 | Bucket not exists | Bucket 不存在 -NoSuchKey | 404 | Object not exists | 文件不存在 -NoSuchUpload | 404 | Multipart upload id not exists | Multipart Upload ID 不存在 -NotImplemented | 501 | Not implemented | 无法处理的方法 -PreconditionFailed | 412 | Pre condition failed | 预处理错误 -RequestTimeTooSkewed | 403 | Request time exceeds 15 minutes to server time | 发起请求的时间和服务器时间超出 15 分钟 -RequestTimeout | 400 | Request timeout | 请求超时 -RequestIsNotMultiPartContent | 400 | Invalid post content-type | Post 请求 content-type 非法 -SignatureDoesNotMatch | 403 | Invalid signature | 签名错误 -TooManyBuckets | 400 | Too many buckets on this user | 用户的 Bucket 数目超过限制 -RequestError | -1 | network error | 网络出现中断或异常 -ConnectionTimeoutError | -2 | request connect timeout | 请求连接超时 -SecurityTokenExpired | 403 | sts Security Token Expired | sts Security Token 超时失效 +| code | status | message | message in Chinese | +| ----------------------------------- | ------ | ------------------------------------------------ | -------------------------------------- | +| AccessDenied | 403 | Access Denied | 拒绝访问 | +| BucketAlreadyExists | 409 | Bucket already exists | Bucket 已经存在 | +| BucketNotEmpty | 409 | Bucket is not empty | Bucket 不为空 | +| RestoreAlreadyInProgress | 409 | The restore operation is in progress. | restore 操作正在进行中 | +| OperationNotSupported | 400 | The operation is not supported for this resource | 该资源暂不支持restore操作 | +| EntityTooLarge | 400 | Entity too large | 实体过大 | +| EntityTooSmall | 400 | Entity too small | 实体过小 | +| FileGroupTooLarge | 400 | File group too large | 文件组过大 | +| InvalidLinkName | 400 | Link name can't be the same as the object name | Object Link 与指向的 Object 同名 | +| LinkPartNotExist | 400 | Can't link to not exists object | Object Link 中指向的 Object 不存在 | +| ObjectLinkTooLarge | 400 | Too many links to this object | Object Link 中 Object 个数过多 | +| FieldItemTooLong | 400 | Post form fields items too large | Post 请求中表单域过大 | +| FilePartInterity | 400 | File part has changed | 文件 Part 已改变 | +| FilePartNotExist | 400 | File part not exists | 文件 Part 不存在 | +| FilePartStale | 400 | File part stale | 文件 Part 过时 | +| IncorrectNumberOfFilesInPOSTRequest | 400 | Post request contains invalid number of files | Post 请求中文件个数非法 | +| InvalidArgument | 400 | Invalid format argument | 参数格式错误 | +| InvalidAccessKeyId | 400 | Access key id not exists | Access Key ID 不存在 | +| InvalidBucketName | 400 | Invalid bucket name | 无效的 Bucket 名字 | +| InvalidDigest | 400 | Invalid digest | 无效的摘要 | +| InvalidEncryptionAlgorithm | 400 | Invalid encryption algorithm | 指定的熵编码加密算法错误 | +| InvalidObjectName | 400 | Invalid object name | 无效的 Object 名字 | +| InvalidPart | 400 | Invalid part | 无效的 Part | +| InvalidPartOrder | 400 | Invalid part order | 无效的 part 顺序 | +| InvalidPolicyDocument | 400 | Invalid policy document | 无效的 Policy 文档 | +| InvalidTargetBucketForLogging | 400 | Invalid bucket on logging operation | Logging 操作中有无效的目标 bucket | +| Internal | 500 | OSS server internal error | OSS 内部发生错误 | +| MalformedXML | 400 | Malformed XML format | XML 格式非法 | +| MalformedPOSTRequest | 400 | Invalid post body format | Post 请求的 body 格式非法 | +| MaxPOSTPreDataLengthExceeded | 400 | Post extra data too large | Post 请求上传文件内容之外的 body 过大 | +| MethodNotAllowed | 405 | Not allowed method | 不支持的方法 | +| MissingArgument | 411 | Missing argument | 缺少参数 | +| MissingContentLength | 411 | Missing `Content-Length` header | 缺少内容长度 | +| NoSuchBucket | 404 | Bucket not exists | Bucket 不存在 | +| NoSuchKey | 404 | Object not exists | 文件不存在 | +| NoSuchUpload | 404 | Multipart upload id not exists | Multipart Upload ID 不存在 | +| NotImplemented | 501 | Not implemented | 无法处理的方法 | +| PreconditionFailed | 412 | Pre condition failed | 预处理错误 | +| RequestTimeTooSkewed | 403 | Request time exceeds 15 minutes to server time | 发起请求的时间和服务器时间超出 15 分钟 | +| RequestTimeout | 400 | Request timeout | 请求超时 | +| RequestIsNotMultiPartContent | 400 | Invalid post content-type | Post 请求 content-type 非法 | +| SignatureDoesNotMatch | 403 | Invalid signature | 签名错误 | +| TooManyBuckets | 400 | Too many buckets on this user | 用户的 Bucket 数目超过限制 | +| RequestError | -1 | network error | 网络出现中断或异常 | +| ConnectionTimeoutError | -2 | request connect timeout | 请求连接超时 | +| SecurityTokenExpired | 403 | sts Security Token Expired | sts Security Token 超时失效 | ## Contributors diff --git a/package.json b/package.json index 46212975a..a5a9d69b5 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,21 @@ }, "description": "Aliyun OSS(Object Storage Service) Node.js Client", "scripts": { - "lint": "eslint src test --ext .ts", - "test": "egg-bin test", - "test-local": "egg-bin test", - "cov": "egg-bin cov", - "ci": "npm run lint && npm run cov && npm run prepublishOnly && attw --pack", - "prepublishOnly": "tshy && tshy-after" + "lint": "oxlint", + "pretest": "npm run lint -- --fix", + "test": "vitest run --test-timeout 15000", + "cov": "npm run test -- --coverage", + "preci": "npm run lint", + "ci": "npm run cov && npm run prepublishOnly && attw --pack", + "prepublishOnly": "tshy && tshy-after", + "prepare": "husky" + }, + "lint-staged": { + "*": "prettier --write --ignore-unknown --cache", + "*.{ts,js,json,md,yml}": [ + "prettier --ignore-unknown --write", + "oxlint --fix" + ] }, "repository": { "type": "git", @@ -42,16 +51,20 @@ "@arethetypeswrong/cli": "^0.15.3", "@eggjs/tsconfig": "^1.1.0", "@types/mime": "^3.0.1", - "@types/mocha": "^10.0.1", "@types/ms": "^0.7.31", "@types/node": "^20.3.1", "@types/xml2js": "^0.4.12", - "egg-bin": "^6.4.1", + "@vitest/coverage-v8": "^3.1.3", "eslint": "^8.25.0", "eslint-config-egg": "^13.0.0", + "husky": "^9.1.7", + "oxlint": "^0.16.10", + "prettier": "^3.5.3", + "read-env-value": "^1.0.1", "tshy": "^1.0.0", "tshy-after": "^1.0.0", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "vitest": "^3.1.3" }, "files": [ "dist", diff --git a/src/OSSBaseClient.ts b/src/OSSBaseClient.ts index 090214ebf..b1ea82196 100644 --- a/src/OSSBaseClient.ts +++ b/src/OSSBaseClient.ts @@ -2,16 +2,28 @@ import { debuglog } from 'node:util'; import assert from 'node:assert'; import { createHash } from 'node:crypto'; import { extname } from 'node:path'; + import { sendToWormhole } from 'stream-wormhole'; import { parseStringPromise } from 'xml2js'; import { encodeURIComponent as safeEncodeURIComponent } from 'utility'; import mime from 'mime'; import { - HttpClient, RequestOptions, HttpClientResponse, IncomingHttpHeaders, + type RequestOptions, + type HttpClientResponse, + type IncomingHttpHeaders, + HttpClient, } from 'urllib'; import ms from 'ms'; -import { authorization, buildCanonicalString, computeSignature } from './util/index.js'; -import { OSSRequestParams, OSSResult, RequestParameters } from './type/Request.js'; +import { + authorization, + buildCanonicalString, + computeSignature, +} from './util/index.js'; +import type { + OSSRequestParams, + OSSResult, + RequestParameters, +} from './type/Request.js'; import { OSSClientError } from './error/index.js'; const debug = debuglog('oss-client:client'); @@ -76,13 +88,22 @@ export abstract class OSSBaseClient { * + CanonicalizedOSSHeaders * + CanonicalizedResource)) */ - protected authorization(method: string, resource: string, headers: IncomingHttpHeaders, subResource?: RequestParameters) { + protected authorization( + method: string, + resource: string, + headers: IncomingHttpHeaders, + subResource?: RequestParameters + ) { const stringToSign = buildCanonicalString(method.toUpperCase(), resource, { headers, parameters: subResource, }); debug('stringToSign: %o', stringToSign); - const auth = authorization(this.options.accessKeyId, this.options.accessKeySecret, stringToSign); + const auth = authorization( + this.options.accessKeyId, + this.options.accessKeySecret, + stringToSign + ); debug('authorization: %o', auth); return auth; } @@ -96,7 +117,9 @@ export abstract class OSSBaseClient { protected abstract getRequestEndpoint(): string; - protected getRequestURL(params: Pick) { + protected getRequestURL( + params: Pick + ) { let resourcePath = '/'; if (params.object) { // Preserve '/' in result url @@ -116,9 +139,9 @@ export abstract class OSSBaseClient { if (typeof params.subResource === 'string') { subresAsQuery[params.subResource] = ''; } else if (Array.isArray(params.subResource)) { - params.subResource.forEach(k => { + for (const k of params.subResource) { subresAsQuery[k] = ''; - }); + } } else { subresAsQuery = params.subResource; } @@ -129,7 +152,7 @@ export abstract class OSSBaseClient { return urlObject.toString(); } - getResource(params: { bucket?: string; object?: string; }) { + getResource(params: { bucket?: string; object?: string }) { let resource = '/'; if (params.bucket) resource += `${params.bucket}/`; if (params.object) resource += params.object; @@ -164,19 +187,30 @@ export abstract class OSSBaseClient { } } if (params.content) { - if (!params.disabledMD5) { - if (!headers['content-md5']) { - headers['content-md5'] = createHash('md5').update(Buffer.from(params.content)).digest('base64'); - } + if (!params.disabledMD5 && !headers['content-md5']) { + headers['content-md5'] = createHash('md5') + .update(Buffer.from(params.content)) + .digest('base64'); } if (!headers['content-length']) { headers['content-length'] = `${params.content.length}`; } } const authResource = this.getResource(params); - headers.authorization = this.authorization(params.method, authResource, headers, params.subResource); + headers.authorization = this.authorization( + params.method, + authResource, + headers, + params.subResource + ); const url = this.getRequestURL(params); - debug('request %s %s, with headers %j, !!stream: %s', params.method, url, headers, !!params.stream); + debug( + 'request %s %s, with headers %j, !!stream: %s', + params.method, + url, + headers, + !!params.stream + ); const timeout = params.timeout ?? this.options.timeout; const options: RequestOptions = { method: params.method, @@ -196,13 +230,21 @@ export abstract class OSSBaseClient { /** * request oss server */ - protected async request(params: OSSRequestParams): Promise> { + // eslint-disable-next-line no-explicit-any + protected async request( + params: OSSRequestParams + ): Promise> { const { url, options } = this.createHttpClientRequestParams(params); const result = await this.#httpClient.request(url, options); - debug('response %s %s, got %s, headers: %j', params.method, url, result.status, result.headers); - let err; + debug( + 'response %s %s, got %s, headers: %j', + params.method, + url, + result.status, + result.headers + ); if (!params.successStatuses?.includes(result.status)) { - err = await this.#createClientException(result); + const err = await this.#createClientException(result); if (params.streaming && result.res) { // consume the response stream await sendToWormhole(result.res); @@ -220,13 +262,15 @@ export abstract class OSSBaseClient { } satisfies OSSResult; } - /** private methods */ #initOptions(options: OSSBaseClientInitOptions) { - assert(options.accessKeyId && options.accessKeySecret, 'require accessKeyId and accessKeySecret'); - assert(options.endpoint, 'require endpoint'); - let timeout = 60000; + assert.ok( + options.accessKeyId && options.accessKeySecret, + 'require accessKeyId and accessKeySecret' + ); + assert.ok(options.endpoint, 'require endpoint'); + let timeout = 60_000; if (options.timeout) { if (typeof options.timeout === 'string') { timeout = ms(options.timeout); @@ -259,31 +303,49 @@ export abstract class OSSBaseClient { return `${sdk} ${platform}`; } + // eslint-disable-next-line no-explicit-any async #xml2json(xml: string | Buffer) { if (Buffer.isBuffer(xml)) { xml = xml.toString(); } debug('xml2json %o', xml); - return await parseStringPromise(xml, { + return (await parseStringPromise(xml, { explicitRoot: false, explicitArray: false, - }) as T; + })) as T; } async #createClientException(result: HttpClientResponse) { let err: OSSClientError; - let requestId = result.headers['x-oss-request-id'] as string ?? ''; + let requestId = (result.headers['x-oss-request-id'] as string) ?? ''; let hostId = ''; const status = result.status; - if (!result.data || !result.data.length) { + if (!result.data || result.data.length === 0) { // HEAD not exists resource if (status === 404) { - err = new OSSClientError(status, 'NoSuchKey', 'Object not exists', requestId, hostId); + err = new OSSClientError( + status, + 'NoSuchKey', + 'Object not exists', + requestId, + hostId + ); } else if (status === 412) { - err = new OSSClientError(status, 'PreconditionFailed', 'Pre condition failed', requestId, hostId); + err = new OSSClientError( + status, + 'PreconditionFailed', + 'Pre condition failed', + requestId, + hostId + ); } else { - err = new OSSClientError(status, 'Unknown', `Unknown error, status=${status}, raw error=${result}`, - requestId, hostId); + err = new OSSClientError( + status, + 'Unknown', + `Unknown error, status=${status}, raw error=${result}`, + requestId, + hostId + ); } } else { const xml = result.data.toString(); @@ -292,12 +354,21 @@ export abstract class OSSBaseClient { let info; try { info = await this.#xml2json(xml); - } catch (e: any) { - err = new OSSClientError(status, 'PreconditionFailed', `${e.message} (raw xml=${JSON.stringify(xml)})`, requestId, hostId); + } catch (e) { + const message = e instanceof Error ? e.message : String(e); + err = new OSSClientError( + status, + 'PreconditionFailed', + `${message} (raw xml=${JSON.stringify(xml)})`, + requestId, + hostId + ); return err; } - let message = info?.Message ?? `Unknown request error, status=${result.status}, raw xml=${JSON.stringify(xml)}`; + let message = + info?.Message ?? + `Unknown request error, status=${result.status}, raw xml=${JSON.stringify(xml)}`; if (info?.Condition) { message += ` (condition=${info.Condition})`; } @@ -307,11 +378,22 @@ export abstract class OSSBaseClient { if (info?.HostId) { hostId = info.HostId; } - err = new OSSClientError(status, info?.Code ?? 'Unknown', message, requestId, hostId); + err = new OSSClientError( + status, + info?.Code ?? 'Unknown', + message, + requestId, + hostId + ); // https://help.aliyun.com/zh/oss/support/http-status-code-409#section-rmc-hvd-j38 - if (info?.Code === 'PositionNotEqualToLength' && result.headers['x-oss-next-append-position']) { - err.nextAppendPosition = result.headers['x-oss-next-append-position'] as string; + if ( + info?.Code === 'PositionNotEqualToLength' && + result.headers['x-oss-next-append-position'] + ) { + err.nextAppendPosition = result.headers[ + 'x-oss-next-append-position' + ] as string; } } @@ -363,4 +445,3 @@ export abstract class OSSBaseClient { // * STS Client class // */ // Client.STS = require('./sts'); - diff --git a/src/OSSObject.ts b/src/OSSObject.ts index 1996dcb23..53744ecfd 100644 --- a/src/OSSObject.ts +++ b/src/OSSObject.ts @@ -1,8 +1,9 @@ -import { Readable, Writable } from 'node:stream'; +import type { Readable, Writable } from 'node:stream'; import { createReadStream, createWriteStream } from 'node:fs'; import { strict as assert } from 'node:assert'; import querystring from 'node:querystring'; import fs from 'node:fs/promises'; + import mime from 'mime'; import { isReadable, isWritable } from 'is-type-of'; import type { IncomingHttpHeaders } from 'urllib'; @@ -25,12 +26,15 @@ import type { GetStreamResult, CopyObjectOptions, CopyAndPutMetaResult, + ObjectMeta, + StorageType, } from 'oss-interface'; + import { - OSSBaseClientInitOptions, + type OSSBaseClientInitOptions, OSSBaseClient, } from './OSSBaseClient.js'; -import { +import type { ACLType, AppendObjectOptions, AppendObjectResult, @@ -59,8 +63,14 @@ import { RequestMethod, } from './type/index.js'; import { - checkBucketName, signatureForURL, encodeCallback, json2xml, timestamp, - checkObjectTag, computeSignature, policyToJSONString, + checkBucketName, + signatureForURL, + encodeCallback, + json2xml, + timestamp, + checkObjectTag, + computeSignature, + policyToJSONString, } from './util/index.js'; export interface OSSObjectClientInitOptions extends OSSBaseClientInitOptions { @@ -72,13 +82,27 @@ export interface OSSObjectClientInitOptions extends OSSBaseClientInitOptions { cname?: boolean; } +interface XMLCommonPrefix { + Prefix: string; +} + +interface XMLContent { + Key: string; + LastModified: string; + ETag: string; + Type: string; + Size: string; + StorageClass: StorageType; + Owner: { ID: string; DisplayName: string }; +} + export class OSSObject extends OSSBaseClient implements IObjectSimple { #bucket: string; #bucketEndpoint: string; constructor(options: OSSObjectClientInitOptions) { if (!options.cname) { - assert(options.bucket, 'bucket required'); + assert.ok(options.bucket, 'bucket required'); } if (options.bucket) { checkBucketName(options.bucket); @@ -89,7 +113,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { this.#bucket = ''; this.#bucketEndpoint = this.options.endpoint; } else { - this.#bucket = options.bucket!; + this.#bucket = options.bucket ?? ''; const urlObject = new URL(this.options.endpoint); urlObject.hostname = `${this.#bucket}.${urlObject.hostname}`; this.#bucketEndpoint = urlObject.toString(); @@ -102,18 +126,29 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * AppendObject * @see https://help.aliyun.com/zh/oss/developer-reference/appendobject */ - async append(name: string, file: string | Buffer | Readable, options?: AppendObjectOptions): Promise { + async append( + name: string, + file: string | Buffer | Readable, + options?: AppendObjectOptions + ): Promise { const position = options?.position ?? '0'; - const result = await this.#sendPutRequest(name, { - ...options, - subResource: { - append: '', - position: `${position}`, + const result = await this.#sendPutRequest( + name, + { + ...options, + subResource: { + append: '', + position: `${position}`, + }, }, - }, file, 'POST'); + file, + 'POST' + ); return { ...result, - nextAppendPosition: result.res.headers['x-oss-next-append-position'] as string, + nextAppendPosition: result.res.headers[ + 'x-oss-next-append-position' + ] as string, }; } @@ -132,9 +167,13 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * key1: 'value1', * key2: 'value2' * } - * @return {Object} result + * @returns {Object} result */ - async put(name: string, file: string | Buffer | Readable, options?: PutObjectOptions): Promise { + async put( + name: string, + file: string | Buffer | Readable, + options?: PutObjectOptions + ): Promise { if (typeof file === 'string' || isReadable(file) || Buffer.isBuffer(file)) { return await this.#sendPutRequest(name, options ?? {}, file); } @@ -144,11 +183,19 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { /** * put an object from ReadableStream. */ - async putStream(name: string, stream: Readable, options?: PutObjectOptions): Promise { + async putStream( + name: string, + stream: Readable, + options?: PutObjectOptions + ): Promise { return await this.#sendPutRequest(name, options ?? {}, stream); } - async putMeta(name: string, meta: UserMeta, options?: Omit) { + async putMeta( + name: string, + meta: UserMeta, + options?: Omit + ) { return await this.copy(name, name, { meta, ...options, @@ -159,22 +206,26 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * GetBucket (ListObjects) * @see https://help.aliyun.com/zh/oss/developer-reference/listobjects */ - async list(query?: ListObjectsQuery, options?: RequestOptions): Promise { + async list( + query?: ListObjectsQuery, + options?: RequestOptions + ): Promise { // prefix, marker, max-keys, delimiter const params = this.#objectRequestParams('GET', '', options); if (query) { params.query = query; } params.xmlResponse = true; - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { data, res } = await this.request(params); - let objects = data.Contents || []; - if (objects) { - if (!Array.isArray(objects)) { - objects = [ objects ]; + let contents = data.Contents as XMLContent[] | XMLContent | undefined; + let objects: ObjectMeta[] = []; + if (contents) { + if (!Array.isArray(contents)) { + contents = [contents]; } - objects = objects.map((obj: any) => ({ + objects = contents.map(obj => ({ name: obj.Key, url: this.#objectUrl(obj.Key), lastModified: obj.LastModified, @@ -188,12 +239,16 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { }, })); } - let prefixes = data.CommonPrefixes || null; - if (prefixes) { - if (!Array.isArray(prefixes)) { - prefixes = [ prefixes ]; + let commonPrefixes = data.CommonPrefixes as + | XMLCommonPrefix[] + | XMLCommonPrefix + | undefined; + let prefixes: string[] = []; + if (commonPrefixes) { + if (!Array.isArray(commonPrefixes)) { + commonPrefixes = [commonPrefixes]; } - prefixes = prefixes.map((item: any) => item.Prefix); + prefixes = commonPrefixes.map(item => item.Prefix); } return { res, @@ -208,12 +263,16 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * ListObjectsV2(GetBucketV2) * @see https://help.aliyun.com/zh/oss/developer-reference/listobjectsv2 */ - async listV2(query?: ListV2ObjectsQuery, options?: RequestOptions): Promise { + async listV2( + query?: ListV2ObjectsQuery, + options?: RequestOptions + ): Promise { const params = this.#objectRequestParams('GET', '', options); params.query = { 'list-type': '2', }; - const continuationToken = query?.['continuation-token'] ?? query?.continuationToken; + const continuationToken = + query?.['continuation-token'] ?? query?.continuationToken; if (continuationToken) { // should set subResource to add sign string params.subResource = { @@ -239,15 +298,16 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { params.query['fetch-owner'] = 'true'; } params.xmlResponse = true; - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { data, res } = await this.request(params); - let objects = data.Contents || []; - if (objects) { - if (!Array.isArray(objects)) { - objects = [ objects ]; + let contents = data.Contents as XMLContent[] | XMLContent | undefined; + let objects: ObjectMeta[] = []; + if (contents) { + if (!Array.isArray(contents)) { + contents = [contents]; } - objects = objects.map((obj: any) => ({ + objects = contents.map(obj => ({ name: obj.Key, url: this.#objectUrl(obj.Key), lastModified: obj.LastModified, @@ -255,25 +315,31 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { type: obj.Type, size: Number(obj.Size), storageClass: obj.StorageClass, - owner: obj.Owner ? { - id: obj.Owner.ID, - displayName: obj.Owner.DisplayName, - } : undefined, + owner: obj.Owner + ? { + id: obj.Owner.ID, + displayName: obj.Owner.DisplayName, + } + : undefined, })); } - let prefixes = data.CommonPrefixes || null; - if (prefixes) { - if (!Array.isArray(prefixes)) { - prefixes = [ prefixes ]; + let commonPrefixes = data.CommonPrefixes as + | XMLCommonPrefix[] + | XMLCommonPrefix + | undefined; + let prefixes: string[] = []; + if (commonPrefixes) { + if (!Array.isArray(commonPrefixes)) { + commonPrefixes = [commonPrefixes]; } - prefixes = prefixes.map((item: any) => item.Prefix); + prefixes = commonPrefixes.map(item => item.Prefix); } return { res, objects, - prefixes: prefixes || [], + prefixes, isTruncated: data.IsTruncated === 'true', - keyCount: parseInt(data.KeyCount), + keyCount: Number.parseInt(data.KeyCount), continuationToken: data.ContinuationToken, nextContinuationToken: data.NextContinuationToken, } satisfies ListV2ObjectResult; @@ -284,8 +350,16 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * @see https://help.aliyun.com/zh/oss/developer-reference/getobject */ async get(name: string, options?: GetObjectOptions): Promise; - async get(name: string, file: string | Writable, options?: GetObjectOptions): Promise; - async get(name: string, file?: string | Writable | GetObjectOptions, options?: GetObjectOptions): Promise { + async get( + name: string, + file: string | Writable, + options?: GetObjectOptions + ): Promise; + async get( + name: string, + file?: string | Writable | GetObjectOptions, + options?: GetObjectOptions + ): Promise { let writeStream: Writable | undefined; let needDestroy = false; @@ -304,7 +378,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { try { const params = this.#objectRequestParams('GET', name, options); params.writeStream = writeStream; - params.successStatuses = [ 200, 206, 304 ]; + params.successStatuses = [200, 206, 304]; result = await this.request(params); if (needDestroy && writeStream) { @@ -325,11 +399,14 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { }; } - async getStream(name: string, options?: GetStreamOptions): Promise { + async getStream( + name: string, + options?: GetStreamOptions + ): Promise { options = this.#formatGetOptions(options); const params = this.#objectRequestParams('GET', name, options); params.streaming = true; - params.successStatuses = [ 200, 206, 304 ]; + params.successStatuses = [200, 206, 304]; const { res } = await this.request(params); return { stream: res, @@ -341,7 +418,11 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * PutObjectACL * @see https://help.aliyun.com/zh/oss/developer-reference/putobjectacl */ - async putACL(name: string, acl: ACLType, options?: PutACLOptions): Promise { + async putACL( + name: string, + acl: ACLType, + options?: PutACLOptions + ): Promise { options = options ?? {}; if (options.subres && !options.subResource) { options.subResource = options.subres; @@ -357,7 +438,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { options.headers['x-oss-object-acl'] = acl; name = this.#objectName(name); const params = this.#objectRequestParams('PUT', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { res } = await this.request(params); return { res, @@ -365,9 +446,9 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } /** - * GetObjectACL - * @see https://help.aliyun.com/zh/oss/developer-reference/getobjectacl - */ + * GetObjectACL + * @see https://help.aliyun.com/zh/oss/developer-reference/getobjectacl + */ async getACL(name: string, options?: GetACLOptions): Promise { options = options ?? {}; if (options.subres && !options.subResource) { @@ -384,7 +465,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { name = this.#objectName(name); const params = this.#objectRequestParams('GET', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; params.xmlResponse = true; const { data, res } = await this.request(params); @@ -438,7 +519,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * DeleteObject * @see https://help.aliyun.com/zh/oss/developer-reference/deleteobject */ - async delete(name: string, options?: DeleteObjectOptions): Promise { + async delete( + name: string, + options?: DeleteObjectOptions + ): Promise { const requestOptions = { timeout: options?.timeout, subResource: {} as Record, @@ -447,7 +531,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { requestOptions.subResource.versionId = options.versionId; } const params = this.#objectRequestParams('DELETE', name, requestOptions); - params.successStatuses = [ 204 ]; + params.successStatuses = [204]; const { res } = await this.request(params); return { res, @@ -462,24 +546,33 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * DeleteMultipleObjects * @see https://help.aliyun.com/zh/oss/developer-reference/deletemultipleobjects */ - async deleteMulti(namesOrObjects: string[] | DeleteMultipleObject[], options?: DeleteMultipleObjectOptions): Promise { + async deleteMulti( + namesOrObjects: string[] | DeleteMultipleObject[], + options?: DeleteMultipleObjectOptions + ): Promise { const objects: DeleteMultipleObjectXML[] = []; - assert(namesOrObjects.length > 0, 'namesOrObjects is empty'); + assert.ok(namesOrObjects.length > 0, 'namesOrObjects is empty'); for (const nameOrObject of namesOrObjects) { if (typeof nameOrObject === 'string') { objects.push({ Key: this.#objectName(nameOrObject) }); } else { - assert(nameOrObject.key, 'key is empty'); - objects.push({ Key: this.#objectName(nameOrObject.key), VersionId: nameOrObject.versionId }); + assert.ok(nameOrObject.key, 'key is empty'); + objects.push({ + Key: this.#objectName(nameOrObject.key), + VersionId: nameOrObject.versionId, + }); } } - const xml = json2xml({ - Delete: { - Quiet: !!options?.quiet, - Object: objects, + const xml = json2xml( + { + Delete: { + Quiet: !!options?.quiet, + Object: objects, + }, }, - }, { headers: true }); + { headers: true } + ); const requestOptions = { timeout: options?.timeout, @@ -492,16 +585,14 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { const params = this.#objectRequestParams('POST', '', requestOptions); params.mime = 'xml'; - params.content = Buffer.from(xml, 'utf-8'); + params.content = Buffer.from(xml, 'utf8'); params.xmlResponse = true; - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { data, res } = await this.request(params); // quiet will return null let deleted = data?.Deleted || []; - if (deleted) { - if (!Array.isArray(deleted)) { - deleted = [ deleted ]; - } + if (deleted && !Array.isArray(deleted)) { + deleted = [deleted]; } return { res, @@ -513,7 +604,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * HeadObject * @see https://help.aliyun.com/zh/oss/developer-reference/headobject */ - async head(name: string, options?: HeadObjectOptions): Promise { + async head( + name: string, + options?: HeadObjectOptions + ): Promise { options = options ?? {}; if (options.subres && !options.subResource) { options.subResource = options.subres; @@ -525,7 +619,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { options.subResource.versionId = options.versionId; } const params = this.#objectRequestParams('HEAD', name, options); - params.successStatuses = [ 200, 304 ]; + params.successStatuses = [200, 304]; const { res } = await this.request(params); const meta: UserMeta = {}; const result = { @@ -535,7 +629,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } satisfies HeadObjectResult; for (const k in res.headers) { if (k.startsWith('x-oss-meta-')) { - const key = k.substring(11); + const key = k.slice(11); meta[key] = res.headers[k] as string; } } @@ -560,7 +654,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } options.subResource.objectMeta = ''; const params = this.#objectRequestParams('HEAD', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { res } = await this.request(params); return { status: res.status, @@ -572,7 +666,11 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * PutSymlink * @see https://help.aliyun.com/zh/oss/developer-reference/putsymlink */ - async putSymlink(name: string, targetName: string, options: PutSymlinkOptions): Promise { + async putSymlink( + name: string, + targetName: string, + options: PutSymlinkOptions + ): Promise { options = options ?? {}; if (!options.subResource) { options.subResource = {}; @@ -593,7 +691,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { name = this.#objectName(name); const params = this.#objectRequestParams('PUT', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { res } = await this.request(params); return { res, @@ -604,7 +702,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * GetSymlink * @see https://help.aliyun.com/zh/oss/developer-reference/getsymlink */ - async getSymlink(name: string, options?: GetSymlinkOptions): Promise { + async getSymlink( + name: string, + options?: GetSymlinkOptions + ): Promise { options = options ?? {}; if (!options.subResource) { options.subResource = {}; @@ -615,13 +716,13 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } name = this.#objectName(name); const params = this.#objectRequestParams('GET', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { res } = await this.request(params); const target = res.headers['x-oss-symlink-target'] as string; const meta: Record = {}; for (const k in res.headers) { if (k.startsWith('x-oss-meta-')) { - const key = k.substring(11); + const key = k.slice(11); meta[key] = res.headers[k] as string; } } @@ -636,7 +737,11 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * PutObjectTagging * @see https://help.aliyun.com/zh/oss/developer-reference/putobjecttagging */ - async putObjectTagging(name: string, tag: Record, options?: PutObjectTaggingOptions): Promise { + async putObjectTagging( + name: string, + tag: Record, + options?: PutObjectTaggingOptions + ): Promise { checkObjectTag(tag); options = options ?? {}; if (!options.subResource) { @@ -648,7 +753,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } name = this.#objectName(name); const params = this.#objectRequestParams('PUT', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const tags: { Key: string; Value: string }[] = []; for (const key in tag) { tags.push({ Key: key, Value: tag[key] }); @@ -675,7 +780,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * GetObjectTagging * @see https://help.aliyun.com/zh/oss/developer-reference/getobjecttagging */ - async getObjectTagging(name: string, options?: GutObjectTaggingOptions): Promise { + async getObjectTagging( + name: string, + options?: GutObjectTaggingOptions + ): Promise { options = options ?? {}; if (!options.subResource) { options.subResource = {}; @@ -686,13 +794,13 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } name = this.#objectName(name); const params = this.#objectRequestParams('GET', name, options); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; params.xmlResponse = true; const { res, data } = await this.request(params); // console.log(data.toString()); let tags = data.TagSet?.Tag; if (tags && !Array.isArray(tags)) { - tags = [ tags ]; + tags = [tags]; } const tag: Record = {}; if (tags) { @@ -711,7 +819,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * DeleteObjectTagging * @see https://help.aliyun.com/zh/oss/developer-reference/deleteobjecttagging */ - async deleteObjectTagging(name: string, options?: DeleteObjectTaggingOptions): Promise { + async deleteObjectTagging( + name: string, + options?: DeleteObjectTaggingOptions + ): Promise { options = options ?? {}; if (!options.subResource) { options.subResource = {}; @@ -722,7 +833,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } name = this.#objectName(name); const params = this.#objectRequestParams('DELETE', name, options); - params.successStatuses = [ 204 ]; + params.successStatuses = [204]; const { res } = await this.request(params); return { @@ -746,7 +857,12 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { object: name, }; const resource = this.getResource(params); - const signRes = signatureForURL(this.options.accessKeySecret, options, resource, expiresTimestamp); + const signRes = signatureForURL( + this.options.accessKeySecret, + options, + resource, + expiresTimestamp + ); const url = this.getRequestURL({ object: name, @@ -766,9 +882,9 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { /** * Get Object url by name - * @param name - object name - * @param baseUrl - If provide `baseUrl`, will use `baseUrl` instead the default `endpoint and bucket`. - * return object url include bucket + * @param {string} name - object name + * @param {string} [baseUrl] - If provide `baseUrl`, will use `baseUrl` instead the default `endpoint and bucket`. + * @returns {string} object url include bucket */ generateObjectUrl(name: string, baseUrl?: string) { const urlObject = new URL(baseUrl ?? this.getRequestEndpoint()); @@ -784,9 +900,8 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { } /** - * @param policy specifies the validity of the fields in the request. - * - * return params.OSSAccessKeyId + * @param {object | string} policy specifies the validity of the fields in the request. + * @returns {object} params.OSSAccessKeyId * params.Signature * params.policy JSON text encoded with UTF-8 and Base64. */ @@ -794,8 +909,14 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { if (typeof policy !== 'object' && typeof policy !== 'string') { throw new TypeError('policy must be JSON string or Object'); } - const policyString = Buffer.from(policyToJSONString(policy), 'utf8').toString('base64'); - const Signature = computeSignature(this.options.accessKeySecret, policyString); + const policyString = Buffer.from( + policyToJSONString(policy), + 'utf8' + ).toString('base64'); + const Signature = computeSignature( + this.options.accessKeySecret, + policyString + ); return { OSSAccessKeyId: this.options.accessKeyId, Signature, @@ -806,9 +927,23 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { /** * Copy an object from sourceName to name. */ - async copy(name: string, sourceName: string, options?: CopyObjectOptions): Promise; - async copy(name: string, sourceName: string, sourceBucket: string, options?: CopyObjectOptions): Promise; - async copy(name: string, sourceName: string, sourceBucket?: string | CopyObjectOptions, options?: CopyObjectOptions): Promise { + async copy( + name: string, + sourceName: string, + options?: CopyObjectOptions + ): Promise; + async copy( + name: string, + sourceName: string, + sourceBucket: string, + options?: CopyObjectOptions + ): Promise; + async copy( + name: string, + sourceName: string, + sourceBucket?: string | CopyObjectOptions, + options?: CopyObjectOptions + ): Promise { if (typeof sourceBucket === 'object') { options = sourceBucket; // 兼容旧版本,旧版本第三个参数为options sourceBucket = undefined; @@ -816,18 +951,19 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { options = options ?? {}; options.headers = options.headers ?? {}; let hasMetadata = !!options.meta; - const REPLACE_HEADERS = [ + const REPLACE_HEADERS = new Set([ 'content-type', 'content-encoding', 'content-language', 'content-disposition', 'cache-control', 'expires', - ]; + ]); for (const key in options.headers) { const lowerCaseKey = key.toLowerCase(); - options.headers[`x-oss-copy-source-${lowerCaseKey}`] = options.headers[key]; - if (REPLACE_HEADERS.includes(lowerCaseKey)) { + options.headers[`x-oss-copy-source-${lowerCaseKey}`] = + options.headers[key]; + if (REPLACE_HEADERS.has(lowerCaseKey)) { hasMetadata = true; } } @@ -843,13 +979,15 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { options.headers['x-oss-copy-source'] = sourceName; const params = this.#objectRequestParams('PUT', name, options); params.xmlResponse = true; - params.successStatuses = [ 200, 304 ]; + params.successStatuses = [200, 304]; const { data, res } = await this.request(params); return { - data: data ? { - etag: data.ETag ?? '', - lastModified: data.LastModified ?? '', - } : null, + data: data + ? { + etag: data.ETag ?? '', + lastModified: data.LastModified ?? '', + } + : null, res, } satisfies CopyAndPutMetaResult; } @@ -858,7 +996,12 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { * 另存为 * @see https://help.aliyun.com/zh/oss/user-guide/sys-or-saveas */ - async processObjectSave(sourceObject: string, targetObject: string, process: string, targetBucket?: string) { + async processObjectSave( + sourceObject: string, + targetObject: string, + process: string, + targetBucket?: string + ) { targetObject = this.#objectName(targetObject); const params = this.#objectRequestParams('POST', sourceObject, { subResource: { @@ -866,13 +1009,15 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { }, }); - const bucketParam = targetBucket ? `,b_${Buffer.from(targetBucket).toString('base64')}` : ''; + const bucketParam = targetBucket + ? `,b_${Buffer.from(targetBucket).toString('base64')}` + : ''; targetObject = Buffer.from(targetObject).toString('base64'); const content = { 'x-oss-process': `${process}|sys/saveas,o_${targetObject}${bucketParam}`, }; params.content = Buffer.from(querystring.stringify(content)); - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const result = await this.request(params); return { @@ -892,6 +1037,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { #getCopySourceName(sourceName: string, bucketName?: string) { if (typeof bucketName === 'string') { sourceName = this.#objectName(sourceName); + // eslint-disable-next-line no-negated-condition } else if (sourceName[0] !== '/') { bucketName = this.#bucket; } else { @@ -906,11 +1052,17 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { return sourceName; } - async #sendPutRequest(name: string, options: PutObjectOptions & { subResource?: Record }, - fileOrBufferOrStream: string | Buffer | Readable, method: RequestMethod = 'PUT') { + async #sendPutRequest( + name: string, + options: PutObjectOptions & { subResource?: Record }, + fileOrBufferOrStream: string | Buffer | Readable, + method: RequestMethod = 'PUT' + ) { options.headers = options.headers ?? {}; if (options.headers['Content-Type'] && !options.headers['content-type']) { - options.headers['content-type'] = options.headers['Content-Type'] as string; + options.headers['content-type'] = options.headers[ + 'Content-Type' + ] as string; delete options.headers['Content-Type']; } name = this.#objectName(name); @@ -942,7 +1094,7 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { params.stream = fileOrBufferOrStream; } params.mime = options.mime; - params.successStatuses = [ 200 ]; + params.successStatuses = [200]; const { res, data } = await this.request(params); const putResult = { @@ -985,8 +1137,11 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { /** * generator request params */ - #objectRequestParams(method: RequestMethod, name: string, - options?: Pick) { + #objectRequestParams( + method: RequestMethod, + name: string, + options?: Pick + ) { name = this.#objectName(name); const params: OSSRequestParams = { object: name, @@ -1003,7 +1158,10 @@ export class OSSObject extends OSSBaseClient implements IObjectSimple { return name.replace(/^\/+/, ''); } - #convertMetaToHeaders(meta: UserMeta | undefined, headers: IncomingHttpHeaders) { + #convertMetaToHeaders( + meta: UserMeta | undefined, + headers: IncomingHttpHeaders + ) { if (!meta) { return; } diff --git a/src/error/OSSClientError.ts b/src/error/OSSClientError.ts index d4a757c4e..bc46b02b2 100644 --- a/src/error/OSSClientError.ts +++ b/src/error/OSSClientError.ts @@ -9,8 +9,16 @@ export class OSSClientError extends Error { hostId?: string; nextAppendPosition?: string; - constructor(status: number, code: string, message: string, requestId?: string, hostId?: string) { - super(`[${REQUEST_ID_KEY}=${requestId}, ${RESPONSE_CODE_KEY}=${code}, ${RESPONSE_HOST_KEY}=${hostId}] ${message}`); + constructor( + status: number, + code: string, + message: string, + requestId?: string, + hostId?: string + ) { + super( + `[${REQUEST_ID_KEY}=${requestId}, ${RESPONSE_CODE_KEY}=${code}, ${RESPONSE_HOST_KEY}=${hostId}] ${message}` + ); this.status = status; this.code = code; this.name = 'OSSClientError'; diff --git a/src/type/Object.ts b/src/type/Object.ts index abc74e741..51c7e7b76 100644 --- a/src/type/Object.ts +++ b/src/type/Object.ts @@ -1,5 +1,10 @@ import type { - DeleteObjectOptions, NormalSuccessResponse, OwnerType, RequestOptions, UserMeta, ListObjectResult, + DeleteObjectOptions, + NormalSuccessResponse, + OwnerType, + RequestOptions, + UserMeta, + ListObjectResult, } from 'oss-interface'; import type { IncomingHttpHeaders } from 'urllib'; @@ -162,7 +167,8 @@ export interface ListV2ObjectsQuery { 'encoding-type'?: 'url' | ''; } -export interface ListV2ObjectResult extends Omit { +export interface ListV2ObjectResult + extends Omit { keyCount: number; /** prev index */ continuationToken?: string; diff --git a/src/type/Request.ts b/src/type/Request.ts index 2a794c447..15e56ccce 100644 --- a/src/type/Request.ts +++ b/src/type/Request.ts @@ -2,9 +2,18 @@ import type { Readable, Writable } from 'node:stream'; import type { ListObjectsQuery } from 'oss-interface'; import type { RawResponseWithMeta, IncomingHttpHeaders } from 'urllib'; -export type RequestParameters = string | string[] | Record; +export type RequestParameters = + | string + | string[] + | Record; export type RequestQuery = Record | ListObjectsQuery; -export type RequestMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; +export type RequestMethod = + | 'GET' + | 'HEAD' + | 'POST' + | 'PUT' + | 'PATCH' + | 'DELETE'; export interface Request { headers: IncomingHttpHeaders; diff --git a/src/util/checkBucketName.ts b/src/util/checkBucketName.ts index 07c089685..3710edf28 100644 --- a/src/util/checkBucketName.ts +++ b/src/util/checkBucketName.ts @@ -1,5 +1,7 @@ export function checkBucketName(name: string, 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_]$/; + 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 TypeError('The bucket must be conform to the specifications'); } diff --git a/src/util/checkObjectTag.ts b/src/util/checkObjectTag.ts index 745bb9f71..5c2cf325d 100644 --- a/src/util/checkObjectTag.ts +++ b/src/util/checkObjectTag.ts @@ -8,15 +8,22 @@ export function checkObjectTag(tag: Record) { if (entries.length > 10) { throw new TypeError('maximum of 10 tags for a object'); } - for (const [ key, value ] of entries) { + for (const [key, value] of entries) { if (typeof key !== 'string' || typeof value !== 'string') { throw new TypeError('the key and value of the tag must be String'); } - if (!ALLOW_STRING_RE.test(key) || (value.length > 0 && !ALLOW_STRING_RE.test(value))) { - throw new TypeError('tag can contain letters, numbers, spaces, and the following symbols: plus sign (+), hyphen (-), equal sign (=), period (.), underscore (_), colon (:), and forward slash (/)'); + if ( + !ALLOW_STRING_RE.test(key) || + (value.length > 0 && !ALLOW_STRING_RE.test(value)) + ) { + throw new TypeError( + 'tag can contain letters, numbers, spaces, and the following symbols: plus sign (+), hyphen (-), equal sign (=), period (.), underscore (_), colon (:), and forward slash (/)' + ); } - if (key.length < 1 || key.length > 128) { - throw new TypeError('tag key can be a minimum of 1 byte and a maximum of 128 bytes in length'); + if (key.length === 0 || key.length > 128) { + throw new TypeError( + 'tag key can be a minimum of 1 byte and a maximum of 128 bytes in length' + ); } if (value.length > 256) { throw new TypeError('tag value can be a maximum of 256 bytes in length'); diff --git a/src/util/encodeCallback.ts b/src/util/encodeCallback.ts index 582ea2643..8af27646e 100644 --- a/src/util/encodeCallback.ts +++ b/src/util/encodeCallback.ts @@ -17,7 +17,9 @@ export function encodeCallback(objectCallback: ObjectCallback) { if (objectCallback.contentType) { data.callbackBodyType = objectCallback.contentType; } - const callbackHeaderValue = Buffer.from(JSON.stringify(data)).toString('base64'); + const callbackHeaderValue = Buffer.from(JSON.stringify(data)).toString( + 'base64' + ); const options: CallbackOptions = { callback: callbackHeaderValue, }; @@ -27,7 +29,9 @@ export function encodeCallback(objectCallback: ObjectCallback) { for (const key in objectCallback.customValue) { callbackVar[`x:${key}`] = objectCallback.customValue[key].toString(); } - options.callbackVar = Buffer.from(JSON.stringify(callbackVar)).toString('base64'); + options.callbackVar = Buffer.from(JSON.stringify(callbackVar)).toString( + 'base64' + ); } return options; } diff --git a/src/util/json2xml.ts b/src/util/json2xml.ts index 186ed139a..3ccba0fe3 100644 --- a/src/util/json2xml.ts +++ b/src/util/json2xml.ts @@ -1,6 +1,9 @@ import { escape as escapeHTML } from 'utility'; -export function json2xml(json: Record, options?: { headers: boolean }) { +export function json2xml( + json: Record, + options?: { headers: boolean } +) { let xml = ''; if (options?.headers) { xml = '\n'; @@ -16,7 +19,7 @@ export function json2xml(json: Record, options?: { headers: boolean } } else if (typeof value === 'object') { xml += `<${key}>`; - xml += json2xml(value); + xml += json2xml(value as Record); xml += ``; } else { xml += `<${key}>${escapeHTML(value.toString())}`; diff --git a/src/util/policyToJSONString.ts b/src/util/policyToJSONString.ts index b5e5c99e0..229e5ac1d 100644 --- a/src/util/policyToJSONString.ts +++ b/src/util/policyToJSONString.ts @@ -3,8 +3,9 @@ export function policyToJSONString(policy: object | string) { if (typeof policy === 'string') { try { policyJSONString = JSON.stringify(JSON.parse(policy)); - } catch (err: any) { - throw new TypeError(`Policy string is not a valid JSON: ${err.message}`); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new TypeError(`Policy string is not a valid JSON: ${message}`); } } else { policyJSONString = JSON.stringify(policy); diff --git a/src/util/sign.ts b/src/util/sign.ts index 2002ebf75..ad7b6f2e2 100644 --- a/src/util/sign.ts +++ b/src/util/sign.ts @@ -1,7 +1,9 @@ import { debuglog } from 'node:util'; import crypto from 'node:crypto'; + import type { IncomingHttpHeaders } from 'urllib'; import type { SignatureUrlOptions } from 'oss-interface'; + import type { Request, RequestParameters } from '../type/Request.js'; import { encodeCallback } from './encodeCallback.js'; @@ -12,7 +14,10 @@ const OSS_PREFIX = 'x-oss-'; * build canonicalized resource * @see https://help.aliyun.com/zh/oss/developer-reference/include-signatures-in-the-authorization-header#section-rvv-dx2-xdb */ -function buildCanonicalizedResource(resourcePath: string, parameters?: RequestParameters) { +function buildCanonicalizedResource( + resourcePath: string, + parameters?: RequestParameters +) { let canonicalizedResource = `${resourcePath}`; let separatorString = '?'; @@ -39,7 +44,9 @@ function buildCanonicalizedResource(resourcePath: string, parameters?: RequestPa } separatorString = '&'; }; - Object.keys(parameters).sort(compareFunc).forEach(processFunc); + for (const key of Object.keys(parameters).sort(compareFunc)) { + processFunc(key); + } } debug('canonicalizedResource: %o', canonicalizedResource); return canonicalizedResource; @@ -55,42 +62,60 @@ function lowercaseKeyHeader(headers: IncomingHttpHeaders) { return lowercaseHeaders; } -export function buildCanonicalString(method: string, resourcePath: string, request: Request, expiresTimestamp?: string) { +export function buildCanonicalString( + method: string, + resourcePath: string, + request: Request, + expiresTimestamp?: string +) { const headers = lowercaseKeyHeader(request.headers); const headersToSign: IncomingHttpHeaders = {}; const signContent: string[] = [ method.toUpperCase(), - headers['content-md5'] as string ?? '', - headers['content-type']!, - expiresTimestamp || headers['x-oss-date'] as string, + (headers['content-md5'] as string) ?? '', + headers['content-type'] as string, + expiresTimestamp || (headers['x-oss-date'] as string), ]; - Object.keys(headers).forEach(key => { + for (const key of Object.keys(headers)) { if (key.startsWith(OSS_PREFIX)) { headersToSign[key] = String(headers[key]).trim(); } - }); + } - Object.keys(headersToSign).sort().forEach(key => { + for (const key of Object.keys(headersToSign).sort()) { signContent.push(`${key}:${headersToSign[key]}`); - }); - signContent.push(buildCanonicalizedResource(resourcePath, request.parameters)); + } + signContent.push( + buildCanonicalizedResource(resourcePath, request.parameters) + ); return signContent.join('\n'); } -export function computeSignature(accessKeySecret: string, canonicalString: string) { +export function computeSignature( + accessKeySecret: string, + canonicalString: string +) { const signature = crypto.createHmac('sha1', accessKeySecret); return signature.update(Buffer.from(canonicalString)).digest('base64'); } -export function authorization(accessKeyId: string, accessKeySecret: string, canonicalString: string) { +export function authorization( + accessKeyId: string, + accessKeySecret: string, + canonicalString: string +) { // https://help.aliyun.com/zh/oss/developer-reference/include-signatures-in-the-authorization-header return `OSS ${accessKeyId}:${computeSignature(accessKeySecret, canonicalString)}`; } -export function signatureForURL(accessKeySecret: string, options: SignatureUrlOptions, - resource: string, expiresTimestamp: number) { +export function signatureForURL( + accessKeySecret: string, + options: SignatureUrlOptions, + resource: string, + expiresTimestamp: number +) { const headers: Record = {}; const subResource = options.subResource ?? {}; @@ -141,10 +166,15 @@ export function signatureForURL(accessKeySecret: string, options: SignatureUrlOp } } - const canonicalString = buildCanonicalString(options.method!, resource, { - headers, - parameters: subResource, - }, `${expiresTimestamp}`); + const canonicalString = buildCanonicalString( + options.method as string, + resource, + { + headers, + parameters: subResource, + }, + `${expiresTimestamp}` + ); return { Signature: computeSignature(accessKeySecret, canonicalString), diff --git a/test/OSSObject.test.ts b/test/OSSObject.test.ts index 584b51fae..47c5ded72 100644 --- a/test/OSSObject.test.ts +++ b/test/OSSObject.test.ts @@ -1,23 +1,42 @@ import { strict as assert } from 'node:assert'; import { fileURLToPath } from 'node:url'; -import { createReadStream, createWriteStream, existsSync, readFileSync } from 'node:fs'; +import { + createReadStream, + createWriteStream, + existsSync, + readFileSync, +} from 'node:fs'; import { readFile, writeFile, stat } from 'node:fs/promises'; import { pipeline } from 'node:stream/promises'; import path from 'node:path'; import os from 'node:os'; import { createHash, randomUUID } from 'node:crypto'; -import { ObjectMeta } from 'oss-interface'; -import urllib, { IncomingHttpHeaders, RawResponseWithMeta } from 'urllib'; + +import { + describe, + it, + beforeAll, + beforeEach, + afterAll, + afterEach, +} from 'vitest'; +import type { ObjectMeta } from 'oss-interface'; +import { + type IncomingHttpHeaders, + type RawResponseWithMeta, + request, +} from 'urllib'; + import config from './config.js'; import { OSSObject } from '../src/index.js'; -import { OSSClientError } from '../src/error/OSSClientError.js'; +import type { OSSClientError } from '../src/error/OSSClientError.js'; import { Readable } from 'node:stream'; describe('test/OSSObject.test.ts', () => { const tmpdir = os.tmpdir(); const prefix = config.prefix; - assert(config.oss.accessKeyId); - assert(config.oss.accessKeySecret); + assert.ok(config.oss.accessKeyId); + assert.ok(config.oss.accessKeySecret); const ossObject = new OSSObject(config.oss); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -28,28 +47,52 @@ describe('test/OSSObject.test.ts', () => { // fun/movie/001.avi // fun/movie/007.avi const listPrefix = `${prefix}oss-client/list/`; - before(async () => { + beforeAll(async () => { await ossObject.put(`${listPrefix}oss.jpg`, Buffer.from('oss.jpg')); - await ossObject.put(`${listPrefix}fun/test.jpg`, Buffer.from('fun/test.jpg')); - await ossObject.put(`${listPrefix}fun/movie/001.avi`, Buffer.from('fun/movie/001.avi')); - await ossObject.put(`${listPrefix}fun/movie/007.avi`, Buffer.from('fun/movie/007.avi')); - await ossObject.put(`${listPrefix}other/movie/007.avi`, Buffer.from('other/movie/007.avi')); - await ossObject.put(`${listPrefix}other/movie/008.avi`, Buffer.from('other/movie/008.avi')); + await ossObject.put( + `${listPrefix}fun/test.jpg`, + Buffer.from('fun/test.jpg') + ); + await ossObject.put( + `${listPrefix}fun/movie/001.avi`, + Buffer.from('fun/movie/001.avi') + ); + await ossObject.put( + `${listPrefix}fun/movie/007.avi`, + Buffer.from('fun/movie/007.avi') + ); + await ossObject.put( + `${listPrefix}other/movie/007.avi`, + Buffer.from('other/movie/007.avi') + ); + await ossObject.put( + `${listPrefix}other/movie/008.avi`, + Buffer.from('other/movie/008.avi') + ); }); function checkObjectProperties(obj: ObjectMeta) { assert.equal(typeof obj.name, 'string'); assert.equal(typeof obj.lastModified, 'string'); assert.equal(typeof obj.etag, 'string'); - assert(obj.type === 'Normal' || obj.type === 'Multipart' || obj.type === 'Appendable' || obj.type === 'Symlink', - `invalid obj.type ${obj.type}`); + assert.ok( + obj.type === 'Normal' || + obj.type === 'Multipart' || + obj.type === 'Appendable' || + obj.type === 'Symlink', + `invalid obj.type ${obj.type}` + ); assert.equal(typeof obj.size, 'number'); // assert.equal(obj.storageClass, 'Standard'); - assert(obj.storageClass === 'Standard' || obj.storageClass === 'IA', - `invalid obj.storageClass ${obj.storageClass}`); - assert.equal(typeof obj.owner, 'object'); - assert.equal(typeof obj.owner!.id, 'string'); - assert.equal(typeof obj.owner!.displayName, 'string'); + assert.ok( + obj.storageClass === 'Standard' || obj.storageClass === 'IA', + `invalid obj.storageClass ${obj.storageClass}` + ); + assert.ok(obj.owner); + assert.ok(obj.owner.id); + assert.ok(obj.owner.displayName); + assert.equal(typeof obj.owner.id, 'string'); + assert.equal(typeof obj.owner.displayName, 'string'); } it('should list with query', async () => { @@ -57,56 +100,61 @@ describe('test/OSSObject.test.ts', () => { prefix: listPrefix, 'max-keys': 5, }); - assert(result.objects.length > 0); + assert.ok(result.objects.length > 0); // console.log(result.objects); result.objects.map(checkObjectProperties); assert.equal(typeof result.nextMarker, 'string'); // console.log(result.isTruncated); - assert(result.isTruncated); + assert.ok(result.isTruncated); assert.deepEqual(result.prefixes, []); - assert(result.res.headers.date); + assert.ok(result.res.headers.date); const obj = result.objects[0]; assert.match(obj.url, /^https:\/\//); - assert(obj.url.endsWith(`/${obj.name}`)); - assert(obj.owner!.id); - assert(obj.size > 0); + assert.ok(obj.url.endsWith(`/${obj.name}`)); + assert.ok(obj.owner); + assert.ok(obj.owner.id); + assert.ok(obj.size > 0); }); it.skip('should list timeout work', async () => { - await assert.rejects(async () => { - await ossObject.list({}, { timeout: 1 }); - }, (err: Error) => { - assert.match(err.message, /Request timeout for 1 ms/); - assert.equal(err.name, 'HttpClientRequestTimeoutError'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.list({}, { timeout: 1 }); + }, + (err: Error) => { + assert.match(err.message, /Request timeout for 1 ms/); + assert.equal(err.name, 'HttpClientRequestTimeoutError'); + return true; + } + ); }); it('should list only 1 object', async () => { const result = await ossObject.list({ 'max-keys': 1, }); - assert(result.objects.length <= 1); + assert.ok(result.objects.length <= 1); result.objects.map(checkObjectProperties); assert.equal(typeof result.nextMarker, 'string'); - assert(result.isTruncated); + assert.ok(result.isTruncated); assert.deepEqual(result.prefixes, []); - assert(result.res.headers.date); + assert.ok(result.res.headers.date); const obj = result.objects[0]; assert.match(obj.url, /^https:\/\//); - assert(obj.url.endsWith(`/${obj.name}`)); - assert(obj.owner!.id); - assert(obj.size > 0); + assert.ok(obj.url.endsWith(`/${obj.name}`)); + assert.ok(obj.owner); + assert.ok(obj.owner.id); + assert.ok(obj.size > 0); }); it('should list top 3 objects', async () => { const result = await ossObject.list({ 'max-keys': 3, }); - assert(result.objects.length <= 3); + assert.ok(result.objects.length <= 3); result.objects.map(checkObjectProperties); assert.equal(typeof result.nextMarker, 'string'); - assert(result.isTruncated); + assert.ok(result.isTruncated); assert.deepEqual(result.prefixes, []); // next 2 @@ -117,7 +165,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result2.objects.length, 2); result.objects.map(checkObjectProperties); assert.equal(typeof result2.nextMarker, 'string'); - assert(result2.isTruncated); + assert.ok(result2.isTruncated); assert.deepEqual(result2.prefixes, []); }); @@ -128,7 +176,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result.objects.length, 2); result.objects.map(checkObjectProperties); assert.equal(result.nextMarker, null); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); result = await ossObject.list({ @@ -137,7 +185,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result.objects.length, 2); result.objects.map(checkObjectProperties); assert.equal(result.nextMarker, null); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); }); @@ -149,8 +197,11 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result.objects.length, 1); result.objects.map(checkObjectProperties); assert.equal(result.nextMarker, null); - assert(!result.isTruncated); - assert.deepEqual(result.prefixes, [ `${listPrefix}fun/`, `${listPrefix}other/` ]); + assert.ok(!result.isTruncated); + assert.deepEqual(result.prefixes, [ + `${listPrefix}fun/`, + `${listPrefix}other/`, + ]); result = await ossObject.list({ prefix: `${listPrefix}fun/`, @@ -159,8 +210,8 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result.objects.length, 1); result.objects.map(checkObjectProperties); assert.equal(result.nextMarker, null); - assert(!result.isTruncated); - assert.deepEqual(result.prefixes, [ `${listPrefix}fun/movie/` ]); + assert.ok(!result.isTruncated); + assert.deepEqual(result.prefixes, [`${listPrefix}fun/movie/`]); result = await ossObject.list({ prefix: `${listPrefix}fun/movie/`, @@ -169,32 +220,56 @@ describe('test/OSSObject.test.ts', () => { assert.equal(result.objects.length, 2); result.objects.map(checkObjectProperties); assert.equal(result.nextMarker, null); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); }); }); describe('listV2()', () => { const listPrefix = `${prefix}oss-client/listV2/`; - before(async () => { + beforeAll(async () => { await ossObject.put(`${listPrefix}oss.jpg`, Buffer.from('oss.jpg')); - await ossObject.put(`${listPrefix}fun/test.jpg`, Buffer.from('fun/test.jpg')); - await ossObject.put(`${listPrefix}fun/movie/001.avi`, Buffer.from('fun/movie/001.avi')); - await ossObject.put(`${listPrefix}fun/movie/007.avi`, Buffer.from('fun/movie/007.avi')); - await ossObject.put(`${listPrefix}other/movie/007.avi`, Buffer.from('other/movie/007.avi')); - await ossObject.put(`${listPrefix}other/movie/008.avi`, Buffer.from('other/movie/008.avi')); + await ossObject.put( + `${listPrefix}fun/test.jpg`, + Buffer.from('fun/test.jpg') + ); + await ossObject.put( + `${listPrefix}fun/movie/001.avi`, + Buffer.from('fun/movie/001.avi') + ); + await ossObject.put( + `${listPrefix}fun/movie/007.avi`, + Buffer.from('fun/movie/007.avi') + ); + await ossObject.put( + `${listPrefix}other/movie/007.avi`, + Buffer.from('other/movie/007.avi') + ); + await ossObject.put( + `${listPrefix}other/movie/008.avi`, + Buffer.from('other/movie/008.avi') + ); }); - function checkObjectProperties(obj: ObjectMeta, options?: { owner: boolean }) { + function checkObjectProperties( + obj: ObjectMeta, + options?: { owner: boolean } + ) { assert.equal(typeof obj.name, 'string'); assert.equal(typeof obj.lastModified, 'string'); assert.equal(typeof obj.etag, 'string'); - assert(obj.type === 'Normal' || obj.type === 'Multipart'); + assert.ok(obj.type === 'Normal' || obj.type === 'Multipart'); assert.equal(typeof obj.size, 'number'); // assert.equal(obj.storageClass, 'Standard'); - assert(obj.storageClass === 'Standard' || obj.storageClass === 'IA'); + assert.ok(obj.storageClass === 'Standard' || obj.storageClass === 'IA'); if (options?.owner) { - assert(typeof obj.owner!.id === 'string' && typeof obj.owner!.displayName === 'string'); + assert.ok(obj.owner); + assert.ok(obj.owner.id); + assert.ok(obj.owner.displayName); + assert.ok( + typeof obj.owner.id === 'string' && + typeof obj.owner.displayName === 'string' + ); } else { assert.equal(obj.owner, undefined); } @@ -205,9 +280,11 @@ describe('test/OSSObject.test.ts', () => { 'max-keys': 1, }); assert.equal(result.objects.length, 1); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result.objects) { + checkObjectProperties(obj); + } assert.equal(typeof result.nextContinuationToken, 'string'); - assert(result.isTruncated); + assert.ok(result.isTruncated); assert.deepEqual(result.prefixes, []); assert.equal(result.keyCount, 1); @@ -217,9 +294,11 @@ describe('test/OSSObject.test.ts', () => { continuationToken: result.nextContinuationToken, }); assert.equal(result2.objects.length, 2); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result2.objects) { + checkObjectProperties(obj); + } assert.equal(typeof result2.nextContinuationToken, 'string'); - assert(result2.isTruncated); + assert.ok(result2.isTruncated); assert.deepEqual(result2.prefixes, []); assert.equal(result2.keyCount, 2); }); @@ -230,18 +309,22 @@ describe('test/OSSObject.test.ts', () => { 'fetch-owner': true, }); assert.equal(result.objects.length, 2); - result.objects.forEach(obj => checkObjectProperties(obj, { owner: true })); + for (const obj of result.objects) { + checkObjectProperties(obj, { owner: true }); + } assert.equal(result.nextContinuationToken, undefined); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); result = await ossObject.listV2({ prefix: `${listPrefix}fun/movie`, }); assert.equal(result.objects.length, 2); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result.objects) { + checkObjectProperties(obj); + } assert.equal(result.nextContinuationToken, undefined); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); }); @@ -251,29 +334,38 @@ describe('test/OSSObject.test.ts', () => { delimiter: '/', }); assert.equal(result.objects.length, 1); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result.objects) { + checkObjectProperties(obj); + } assert.equal(result.nextContinuationToken, undefined); - assert(!result.isTruncated); - assert.deepEqual(result.prefixes, [ `${listPrefix}fun/`, `${listPrefix}other/` ]); + assert.ok(!result.isTruncated); + assert.deepEqual(result.prefixes, [ + `${listPrefix}fun/`, + `${listPrefix}other/`, + ]); result = await ossObject.listV2({ prefix: `${listPrefix}fun/`, delimiter: '/', }); assert.equal(result.objects.length, 1); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result.objects) { + checkObjectProperties(obj); + } assert.equal(result.nextContinuationToken, undefined); - assert(!result.isTruncated); - assert.deepEqual(result.prefixes, [ `${listPrefix}fun/movie/` ]); + assert.ok(!result.isTruncated); + assert.deepEqual(result.prefixes, [`${listPrefix}fun/movie/`]); result = await ossObject.listV2({ prefix: `${listPrefix}fun/movie/`, delimiter: '/', }); assert.equal(result.objects.length, 2); - result.objects.forEach(obj => checkObjectProperties(obj)); + for (const obj of result.objects) { + checkObjectProperties(obj); + } assert.equal(result.nextContinuationToken, undefined); - assert(!result.isTruncated); + assert.ok(!result.isTruncated); assert.deepEqual(result.prefixes, []); }); @@ -282,21 +374,21 @@ describe('test/OSSObject.test.ts', () => { 'start-after': `${listPrefix}fun`, 'max-keys': 1, }); - assert(result.objects[0].name === `${listPrefix}fun/movie/001.avi`); + assert.ok(result.objects[0].name === `${listPrefix}fun/movie/001.avi`); result = await ossObject.listV2({ 'start-after': `${listPrefix}fun/movie/001.avi`, 'max-keys': 1, }); - assert(result.objects[0].name === `${listPrefix}fun/movie/007.avi`); + assert.ok(result.objects[0].name === `${listPrefix}fun/movie/007.avi`); result = await ossObject.listV2({ delimiter: '/', prefix: `${listPrefix}fun/movie/`, 'start-after': `${listPrefix}fun/movie/002.avi`, }); - assert(result.objects.length === 1); - assert(result.objects[0].name === `${listPrefix}fun/movie/007.avi`); + assert.ok(result.objects.length === 1); + assert.ok(result.objects[0].name === `${listPrefix}fun/movie/007.avi`); result = await ossObject.listV2({ prefix: `${listPrefix}`, @@ -334,7 +426,7 @@ describe('test/OSSObject.test.ts', () => { }); if (nextContinuationToken) { // should has prev index - assert(result.continuationToken); + assert.ok(result.continuationToken); } keyCount += result.keyCount; nextContinuationToken = result.nextContinuationToken; @@ -354,8 +446,8 @@ describe('test/OSSObject.test.ts', () => { assert.equal(object.res.status, 200); assert.equal(object.nextAppendPosition, '3'); assert.equal(object.res.headers['x-oss-next-append-position'], '3'); - assert(object.url); - assert(object.name); + assert.ok(object.url); + assert.ok(object.name); let res = await ossObject.get(name); assert.equal(res.content.toString(), 'foo'); @@ -406,16 +498,19 @@ describe('test/OSSObject.test.ts', () => { it('should error when position not match', async () => { await ossObject.append(name, Buffer.from('foo')); - await assert.rejects(async () => { - await ossObject.append(name, Buffer.from('foo')); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'PositionNotEqualToLength'); - assert.equal(err.status, 409); - assert.equal(err.nextAppendPosition, '3'); - assert.match(err.message, /Position is not equal to file length/); - return true; - }); + await assert.rejects( + async () => { + await ossObject.append(name, Buffer.from('foo')); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'PositionNotEqualToLength'); + assert.equal(err.status, 409); + assert.equal(err.nextAppendPosition, '3'); + assert.match(err.message, /Position is not equal to file length/); + return true; + } + ); }); it('should use nextAppendPosition to append next', async () => { @@ -518,21 +613,24 @@ describe('test/OSSObject.test.ts', () => { assert.equal(object2.name, name); // put with callback fail - await assert.rejects(async () => { - await ossObject.put(name, __filename, { - callback: { - url: 'https://help.aliyun.com/zh/oss/support/0007-00000205', - body: 'foo=bar', - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'CallbackFailed'); - assert(err.hostId); - assert(err.requestId); - assert.match(err.message, /Response body is not valid json format\./); - return true; - }); + await assert.rejects( + async () => { + await ossObject.put(name, __filename, { + callback: { + url: 'https://help.aliyun.com/zh/oss/support/0007-00000205', + body: 'foo=bar', + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'CallbackFailed'); + assert.ok(err.hostId); + assert.ok(err.requestId); + assert.match(err.message, /Response body is not valid json format\./); + return true; + } + ); // delete the new file const result = await ossObject.delete(name); @@ -541,7 +639,10 @@ describe('test/OSSObject.test.ts', () => { it('should add object with content buffer', async () => { name = `${prefix}oss-client/oss/put-buffer`; - const object = await ossObject.put(`/${name}`, Buffer.from('foo content')); + const object = await ossObject.put( + `/${name}`, + Buffer.from('foo content') + ); assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); assert.equal(typeof object.res.rt, 'number'); assert.equal(object.name, name); @@ -555,30 +656,36 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof object1.res.rt, 'number'); assert.equal(object1.name, name2); let o = await ossObject.head(name1); - assert(o); + assert.ok(o); assert.equal(o.status, 200); await ossObject.delete(name1); - await assert.rejects(async () => { - await ossObject.head(name1); - }, (err: any) => { - assert.equal(err.code, 'NoSuchKey'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.head(name1); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + return true; + } + ); const object2 = await ossObject.put(name2, Buffer.from('foo content')); assert.equal(typeof object2.res.headers['x-oss-request-id'], 'string'); assert.equal(typeof object2.res.rt, 'number'); assert.equal(object2.name, name2); o = await ossObject.head(name2); - assert(o); + assert.ok(o); assert.equal(o.status, 200); await ossObject.delete(name2); - await assert.rejects(async () => { - await ossObject.head(name2); - }, (err: any) => { - assert.equal(err.code, 'NoSuchKey'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.head(name2); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + return true; + } + ); }); it('should add object with readstream', async () => { @@ -592,6 +699,7 @@ describe('test/OSSObject.test.ts', () => { it('should add object with Readable', async () => { name = `${prefix}oss-client/oss/put-Readable`; + // oxlint-disable-next-line consistent-function-scoping async function* generate() { yield 'Hello, '; yield '你好 OSS'; @@ -599,7 +707,10 @@ describe('test/OSSObject.test.ts', () => { const readable = Readable.from(generate()); const object = await ossObject.put(name, readable, { headers: { - 'content-length': Buffer.byteLength('Hello, 你好 OSS', 'utf-8').toString(), + 'content-length': Buffer.byteLength( + 'Hello, 你好 OSS', + 'utf8' + ).toString(), }, }); assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); @@ -638,7 +749,7 @@ describe('test/OSSObject.test.ts', () => { 'Content-Disposition': 'ascii-name.js', }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); assert.equal(info.res.headers['content-disposition'], 'ascii-name.js'); }); @@ -650,9 +761,12 @@ describe('test/OSSObject.test.ts', () => { 'Content-Disposition': encodeURIComponent('non-ascii-名字.js'), }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); - assert.equal(info.res.headers['content-disposition'], 'non-ascii-%E5%90%8D%E5%AD%97.js'); + assert.equal( + info.res.headers['content-disposition'], + 'non-ascii-%E5%90%8D%E5%AD%97.js' + ); }); it('should set Expires', async () => { @@ -662,7 +776,7 @@ describe('test/OSSObject.test.ts', () => { Expires: '1000000', }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); assert.equal(info.res.headers.expires, '1000000'); }); @@ -674,7 +788,7 @@ describe('test/OSSObject.test.ts', () => { 'Content-Type': 'text/plain; charset=gbk', }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); assert.equal(info.res.headers['content-type'], 'text/plain; charset=gbk'); }); @@ -686,9 +800,12 @@ describe('test/OSSObject.test.ts', () => { 'content-type': 'application/javascript; charset=utf8', }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); - assert.equal(info.res.headers['content-type'], 'application/javascript; charset=utf8'); + assert.equal( + info.res.headers['content-type'], + 'application/javascript; charset=utf8' + ); }); it('should set custom Content-MD5 and ignore case', async () => { @@ -714,11 +831,15 @@ describe('test/OSSObject.test.ts', () => { 'Content-Type': 'text/plain; charset=gbk', }, }); - assert(object.name, name); + assert.ok(object.name, name); const info = await ossObject.head(name); - const url = (info.res as any).requestUrls[0]; + const url = (info.res as unknown as { requestUrls: string[] }) + .requestUrls[0]; const urlObject = new URL(url); - assert.equal(urlObject.pathname, `/${prefix}ali-sdkhahhhh%2Boss%2Bmm%20xxx.js`); + assert.equal( + urlObject.pathname, + `/${prefix}ali-sdkhahhhh%2Boss%2Bmm%20xxx.js` + ); assert.equal(info.res.headers['content-type'], 'text/plain; charset=gbk'); }); @@ -727,27 +848,36 @@ describe('test/OSSObject.test.ts', () => { name = `${prefix}put/testsan`; const resultPut = await ossObject.put(name, body); assert.equal(resultPut.res.status, 200); - await assert.rejects(async () => { - await ossObject.put(name, body, { - headers: { 'x-oss-forbid-overwrite': 'true' }, - }); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'FileAlreadyExists'); - assert.match(err.message, /The object you specified already exists and can not be overwritten\./); - return true; - }); + await assert.rejects( + async () => { + await ossObject.put(name, body, { + headers: { 'x-oss-forbid-overwrite': 'true' }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'FileAlreadyExists'); + assert.match( + err.message, + /The object you specified already exists and can not be overwritten\./ + ); + return true; + } + ); }); it('should throw error when path is not file ', async () => { const file = __dirname; name = `${prefix}put/testpathnotfile`; - await assert.rejects(async () => { - await ossObject.put(name, file); - }, (err: Error) => { - assert.equal(`${__dirname} is not file`, err.message); - return true; - }); + await assert.rejects( + async () => { + await ossObject.put(name, file); + }, + (err: Error) => { + assert.equal(`${__dirname} is not file`, err.message); + return true; + } + ); }); }); @@ -759,12 +889,15 @@ describe('test/OSSObject.test.ts', () => { it('should add object with streaming way', async () => { name = `${prefix}oss-client/oss/putStream-localfile.js`; - const object = await ossObject.putStream(name, createReadStream(__filename)); + const object = await ossObject.putStream( + name, + createReadStream(__filename) + ); assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); assert.equal(typeof object.res.rt, 'number'); assert.equal(object.res.size, 0); assert.equal(object.name, name); - assert(object.url); + assert.ok(object.url); // check content const r = await ossObject.get(name); @@ -772,17 +905,21 @@ describe('test/OSSObject.test.ts', () => { const stats = await stat(__filename); assert.equal(r.res.headers['content-length'], `${stats.size}`); assert.equal(r.res.status, 200); - assert((r.res as RawResponseWithMeta).timing.contentDownload > 0); - assert(r.content); + assert.ok((r.res as RawResponseWithMeta).timing.contentDownload > 0); + assert.ok(r.content); assert.equal(r.content.toString(), await readFile(__filename, 'utf8')); }); it('should add image with file streaming way', async () => { name = `${prefix}oss-client/oss/nodejs-1024x768.png`; const imagePath = path.join(__dirname, 'nodejs-1024x768.png'); - const object = await ossObject.putStream(name, createReadStream(imagePath), { - mime: 'image/png', - }); + const object = await ossObject.putStream( + name, + createReadStream(imagePath), + { + mime: 'image/png', + } + ); assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); assert.equal(typeof object.res.rt, 'number'); assert.equal(object.res.size, 0); @@ -811,7 +948,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(r.res.headers['content-type'], 'image/png'); const buf = await readFile(imagePath); assert.equal(r.res.headers['content-length'], `${buf.length}`); - assert(r.content); + assert.ok(r.content); assert.equal(r.content.length, buf.length); assert.deepEqual(r.content, buf); }); @@ -820,9 +957,11 @@ describe('test/OSSObject.test.ts', () => { name = `${prefix}oss-client/oss/nodejs-1024x768.png`; const nameCpy = `${prefix}oss-client/oss/nodejs-1024x768`; const imagePath = path.join(__dirname, 'nodejs-1024x768.png'); - await ossObject.putStream(name, createReadStream(imagePath), { mime: 'image/png' }); + await ossObject.putStream(name, createReadStream(imagePath), { + mime: 'image/png', + }); const signUrl = ossObject.signatureUrl(name, { expires: 3600 }); - const { res: httpStream, status } = await urllib.request(signUrl, { + const { res: httpStream, status } = await request(signUrl, { dataType: 'stream', }); assert.equal(httpStream.headers['content-type'], 'image/png'); @@ -832,8 +971,14 @@ describe('test/OSSObject.test.ts', () => { assert.equal(putResult.res.status, 200); const getResult = await ossObject.get(nameCpy); assert.equal(getResult.res.status, 200); - assert.equal(getResult.res.headers['content-type'], 'application/octet-stream'); - assert.equal(getResult.res.headers['content-length'], httpStream.headers['content-length']); + assert.equal( + getResult.res.headers['content-type'], + 'application/octet-stream' + ); + assert.equal( + getResult.res.headers['content-length'], + httpStream.headers['content-length'] + ); assert.equal(getResult.res.headers.etag, putResult.res.headers.etag); assert.equal(getResult.res.headers.etag, httpStream.headers.etag); }); @@ -854,26 +999,29 @@ describe('test/OSSObject.test.ts', () => { assert.equal(r.res.headers['content-type'], 'application/octet-stream'); assert.equal(r.res.size, 4 * 1024 * 1024); const buf = await readFile(bigFile); - assert(r.content); + assert.ok(r.content); assert.equal(r.content.length, buf.length); assert.deepEqual(r.content, buf); }); it('should throw error with stream destroy', async () => { name = `${prefix}oss-client/oss/putStream-source-destroy.js`; - await assert.rejects(async () => { - const readerStream = createReadStream(`${__filename}.notexists.js`); - await ossObject.putStream(name, readerStream); - }, (err: any) => { - assert.strictEqual(err.status, -1); - return true; - }); + await assert.rejects( + async () => { + const readerStream = createReadStream(`${__filename}.notexists.js`); + await ossObject.putStream(name, readerStream); + }, + (err: OSSClientError) => { + assert.strictEqual(err.status, -1); + return true; + } + ); }); }); describe('putMeta()', () => { let name: string; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/putMeta.js`; const object = await ossObject.put(name, __filename, { meta: { @@ -885,7 +1033,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); @@ -895,26 +1043,29 @@ describe('test/OSSObject.test.ts', () => { }); const info = await ossObject.head(name); assert.equal(info.meta.uid, '2'); - assert(!info.meta.pid); - assert(!info.meta.slus); + assert.ok(!info.meta.pid); + assert.ok(!info.meta.slus); }); it('should throw NoSuchKeyError when update not exists object meta', async () => { - await assert.rejects(async () => { - await ossObject.putMeta(`${name}not-exists`, { - uid: '2', - }); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - return true; - }); + await assert.rejects( + async () => { + await ossObject.putMeta(`${name}not-exists`, { + uid: '2', + }); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + return true; + } + ); }); }); describe('putACL(), getACL()', () => { let name: string; - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); @@ -926,9 +1077,9 @@ describe('test/OSSObject.test.ts', () => { const r2 = await ossObject.getACL(name); assert.equal(r2.res.status, 200); assert.equal(r2.acl, 'default'); - assert(r2.owner); - assert(r2.owner.displayName); - assert(r2.owner.id); + assert.ok(r2.owner); + assert.ok(r2.owner.displayName); + assert.ok(r2.owner.id); // public-read, Put public object acl is not allowed const r3 = await ossObject.putACL(name, 'private'); @@ -980,7 +1131,7 @@ describe('test/OSSObject.test.ts', () => { await ossObject.put(name, __filename); }); - after(async () => { + afterAll(async () => { for (const name of names) { await ossObject.delete(name); } @@ -990,16 +1141,18 @@ describe('test/OSSObject.test.ts', () => { const result = await ossObject.deleteMulti(names); assert.deepEqual( result.deleted.map(v => v.Key), - names, + names ); assert.equal(result.res.status, 200); }); it('should delete 2 exists and 2 not exists objs', async () => { - const result = await ossObject.deleteMulti(names.slice(0, 2).concat([ 'not-exist1', 'not-exist2' ])); + const result = await ossObject.deleteMulti( + names.slice(0, 2).concat(['not-exist1', 'not-exist2']) + ); assert.deepEqual( result.deleted.map(v => v.Key), - names.slice(0, 2).concat([ 'not-exist1', 'not-exist2' ]), + names.slice(0, 2).concat(['not-exist1', 'not-exist2']) ); assert.equal(result.res.status, 200); }); @@ -1008,7 +1161,7 @@ describe('test/OSSObject.test.ts', () => { const result = await ossObject.deleteMulti(names.slice(0, 1)); assert.deepEqual( result.deleted.map(v => v.Key), - names.slice(0, 1), + names.slice(0, 1) ); assert.equal(result.res.status, 200); }); @@ -1025,7 +1178,7 @@ describe('test/OSSObject.test.ts', () => { describe('head()', () => { let name: string; let resHeaders: IncomingHttpHeaders; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/head-meta.js`; const object = await ossObject.put(name, __filename, { meta: { @@ -1038,24 +1191,27 @@ describe('test/OSSObject.test.ts', () => { resHeaders = object.res.headers; }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); it('should head not exists object throw NoSuchKey', async () => { - await assert.rejects(async () => { - await ossObject.head(`${name}not-exists`); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - assert.equal(typeof err.requestId, 'string'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.head(`${name}not-exists`); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + assert.equal(typeof err.requestId, 'string'); + return true; + } + ); }); it('should head exists object with If-Modified-Since < object modified time', async () => { - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); const info = await ossObject.head(name, { headers: { @@ -1063,7 +1219,7 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(info.status, 200); - assert(info.meta); + assert.ok(info.meta); assert.deepEqual(info.meta, { pid: '123', slus: 'test.html', uid: '1' }); }); @@ -1078,7 +1234,7 @@ describe('test/OSSObject.test.ts', () => { }); it('should head exists object with If-Modified-Since > object modified time', async () => { - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const info = await ossObject.head(name, { @@ -1091,20 +1247,23 @@ describe('test/OSSObject.test.ts', () => { }); it('should head exists object with If-Unmodified-Since < object modified time', async () => { - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); - await assert.rejects(async () => { - await ossObject.head(name, { - headers: { - 'If-Unmodified-Since': lastYear.toUTCString(), - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'PreconditionFailed'); - assert.equal(err.status, 412); - return true; - }); + await assert.rejects( + async () => { + await ossObject.head(name, { + headers: { + 'If-Unmodified-Since': lastYear.toUTCString(), + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'PreconditionFailed'); + assert.equal(err.status, 412); + return true; + } + ); }); it('should head exists object with If-Unmodified-Since = object modified time', async () => { @@ -1114,11 +1273,11 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(info.status, 200); - assert(info.meta); + assert.ok(info.meta); }); it('should head exists object with If-Unmodified-Since > object modified time', async () => { - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const info = await ossObject.head(name, { headers: { @@ -1126,7 +1285,7 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(info.status, 200); - assert(info.meta); + assert.ok(info.meta); }); it('should head exists object with If-Match equal etag', async () => { @@ -1142,18 +1301,21 @@ describe('test/OSSObject.test.ts', () => { }); it('should head exists object with If-Match not equal etag', async () => { - await assert.rejects(async () => { - await ossObject.head(name, { - headers: { - 'If-Match': '"foo-etag"', - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.name, 'OSSClientError'); - assert.equal(err.code, 'PreconditionFailed'); - assert.equal(err.status, 412); - return true; - }); + await assert.rejects( + async () => { + await ossObject.head(name, { + headers: { + 'If-Match': '"foo-etag"', + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.name, 'OSSClientError'); + assert.equal(err.code, 'PreconditionFailed'); + assert.equal(err.status, 412); + return true; + } + ); }); it('should head exists object with If-None-Match equal etag', async () => { @@ -1162,7 +1324,7 @@ describe('test/OSSObject.test.ts', () => { 'If-None-Match': resHeaders.etag, }, }); - assert(info.meta); + assert.ok(info.meta); assert.equal(info.status, 304); }); @@ -1182,7 +1344,7 @@ describe('test/OSSObject.test.ts', () => { describe('signatureUrl() and asyncSignatureUrl()', () => { let name: string; let needEscapeName: string; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/signatureUrl.js`; let object = await ossObject.put(name, __filename, { meta: { @@ -1204,14 +1366,14 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); it('should signature url get object ok', async () => { const result = await ossObject.get(name); const url = ossObject.signatureUrl(name); - const urlRes = await urllib.request(url); + const urlRes = await request(url); assert.equal(urlRes.status, 200); assert.equal(urlRes.data.toString(), result.content.toString()); }); @@ -1219,7 +1381,7 @@ describe('test/OSSObject.test.ts', () => { it('should asyncSignatureUrl get object ok', async () => { const result = await ossObject.get(name); const url = await ossObject.asyncSignatureUrl(name); - const urlRes = await urllib.request(url); + const urlRes = await request(url); assert.equal(urlRes.status, 200); assert.equal(urlRes.data.toString(), result.content.toString()); }); @@ -1230,8 +1392,8 @@ describe('test/OSSObject.test.ts', () => { 'content-language': 'zh-cn', }; const url = ossObject.signatureUrl(name, { response }); - assert(url.includes('response-content-type=xml')); - assert(url.includes('response-content-language=zh-cn')); + assert.ok(url.includes('response-content-type=xml')); + assert.ok(url.includes('response-content-language=zh-cn')); }); it('should signature url with options contains other parameters', async () => { @@ -1252,7 +1414,7 @@ describe('test/OSSObject.test.ts', () => { const signUrl = ossObject.signatureUrl(imageName, options); assert.match(signUrl, /x-oss-process=image%2Fresize%2Cw_20/); - const urlRes = await urllib.request(signUrl); + const urlRes = await request(signUrl); assert.equal(urlRes.status, 200); }); @@ -1263,15 +1425,20 @@ describe('test/OSSObject.test.ts', () => { mime: 'image/png', }); - const signUrl = ossObject.signatureUrl(imageName, { expires: 3600, process: 'image/resize,w_200' }); + const signUrl = ossObject.signatureUrl(imageName, { + expires: 3600, + process: 'image/resize,w_200', + }); assert.match(signUrl, /x-oss-process=image%2Fresize%2Cw_200/); - const urlRes = await urllib.request(signUrl); + const urlRes = await request(signUrl); assert.equal(urlRes.status, 200); }); it('should signature url for PUT', async () => { const putString = 'Hello World'; - const contentMD5 = createHash('md5').update(Buffer.from(putString, 'utf8')).digest('base64'); + const contentMD5 = createHash('md5') + .update(Buffer.from(putString, 'utf8')) + .digest('base64'); const url = ossObject.signatureUrl(name, { method: 'PUT', 'Content-Type': 'text/plain; charset=UTF-8', @@ -1282,19 +1449,26 @@ describe('test/OSSObject.test.ts', () => { 'Content-MD5': contentMD5, }; // console.log('%o', url); - const res = await urllib.request(url, { method: 'PUT', data: putString, headers, dataType: 'text' }); + const res = await request(url, { + method: 'PUT', + data: putString, + headers, + dataType: 'text', + }); // console.log(res.data); assert.equal(res.status, 200); const headRes = await ossObject.head(name); assert.equal(headRes.status, 200); - assert.equal(headRes.res.headers.etag, - `"${Buffer.from(contentMD5, 'base64').toString('hex').toUpperCase()}"`); + assert.equal( + headRes.res.headers.etag, + `"${Buffer.from(contentMD5, 'base64').toString('hex').toUpperCase()}"` + ); }); it('should signature url get need escape object ok', async () => { const result = await ossObject.get(needEscapeName); const url = ossObject.signatureUrl(needEscapeName); - const urlRes = await urllib.request(url); + const urlRes = await request(url); assert.equal(urlRes.data.toString(), result.content.toString()); }); @@ -1318,18 +1492,18 @@ describe('test/OSSObject.test.ts', () => { trafficLimit: 8 * 1024 * 100 * 4, method: 'PUT', }); - let result = await urllib.request(url, { + let result = await request(url, { method: 'PUT', content: content1mb, - timeout: 600000, + timeout: 600_000, }); assert.equal(200, result.status); url = ossObject.signatureUrl(limitName, { trafficLimit: 8 * 1024 * 100 * 4, }); - result = await urllib.request(url, { - timeout: 600000, + result = await request(url, { + timeout: 600_000, }); assert.equal(200, result.status); assert.equal(result.headers['content-length'], size.toString()); @@ -1340,7 +1514,7 @@ describe('test/OSSObject.test.ts', () => { let name: string; let resHeaders: IncomingHttpHeaders; let needEscapeName: string; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/get-meta.js`; let object = await ossObject.put(name, __filename, { meta: { @@ -1363,74 +1537,105 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); it('should store object to local file', async () => { - const savePath = path.join(tmpdir, name.replace(/\//g, '-')); + const savePath = path.join(tmpdir, name.replaceAll('/', '-')); const result = await ossObject.get(name, savePath); assert.equal(result.res.status, 200); - assert(!(result.res as any).requestUrls[0].includes('response-cache-control=no-cache')); - assert.equal((await stat(savePath)).size, (await stat(__filename)).size); + assert.ok( + !( + result.res as unknown as { requestUrls: string[] } + ).requestUrls[0].includes('response-cache-control=no-cache') + ); + const { size } = await stat(__filename); + const { size: saveSize } = await stat(savePath); + assert.equal(saveSize, size); }); it('should escape uri path ok', async () => { - const savePath = path.join(tmpdir, needEscapeName.replace(/\//g, '-')); + const savePath = path.join(tmpdir, needEscapeName.replaceAll('/', '-')); const result = await ossObject.get(needEscapeName, savePath); assert.equal(result.res.status, 200); - assert.equal((await stat(savePath)).size, (await stat(__filename)).size); + const { size: saveSize } = await stat(savePath); + const { size: fileSize } = await stat(__filename); + assert.equal(saveSize, fileSize); }); it.skip('should throw error when save path parent dir not exists', async () => { - const savePath = path.join(tmpdir, 'not-exists', name.replace(/\//g, '-')); - await assert.rejects(async () => { - await ossObject.get(name, savePath); - }, (err: Error) => { - assert(err.message.includes('ENOENT')); - return true; - }); + const savePath = path.join( + tmpdir, + 'not-exists', + name.replaceAll('/', '-') + ); + await assert.rejects( + async () => { + await ossObject.get(name, savePath); + }, + (err: Error) => { + assert.ok(err.message.includes('ENOENT')); + return true; + } + ); }); it('should store object to writeStream', async () => { - const savePath = path.join(tmpdir, name.replace(/\//g, '-')); + const savePath = path.join(tmpdir, name.replaceAll('/', '-')); const result = await ossObject.get(name, createWriteStream(savePath)); assert.equal(result.res.status, 200); - assert.equal((await stat(savePath)).size, (await stat(__filename)).size); + const { size } = await stat(__filename); + const { size: saveSize } = await stat(savePath); + assert.equal(saveSize, size); }); it('should store not exists object to file', async () => { - const savePath = path.join(tmpdir, name.replace(/\//g, '-')); - await assert.rejects(async () => { - await ossObject.get(`${name}not-exists`, savePath); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - assert(!existsSync(savePath)); - return true; - }); + const savePath = path.join(tmpdir, name.replaceAll('/', '-')); + await assert.rejects( + async () => { + await ossObject.get(`${name}not-exists`, savePath); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + assert.ok(!existsSync(savePath)); + return true; + } + ); }); it.skip('should throw error when writeStream emit error', async () => { - const savePath = path.join(tmpdir, 'not-exists-dir', name.replace(/\//g, '-')); - await assert.rejects(async () => { - await ossObject.get(name, createWriteStream(savePath)); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - assert(!existsSync(savePath)); - return true; - }); + const savePath = path.join( + tmpdir, + 'not-exists-dir', + name.replaceAll('/', '-') + ); + await assert.rejects( + async () => { + await ossObject.get(name, createWriteStream(savePath)); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + assert.ok(!existsSync(savePath)); + return true; + } + ); }); it('should get object content buffer', async () => { let result = await ossObject.get(name); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); - assert(result.content.toString().includes('oss-client/oss/get-meta.js')); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok( + result.content.toString().includes('oss-client/oss/get-meta.js') + ); result = await ossObject.get(name, undefined); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); - assert(result.content.toString().includes('oss-client/oss/get-meta.js')); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok( + result.content.toString().includes('oss-client/oss/get-meta.js') + ); }); it('should get object content buffer with image process', async () => { @@ -1441,9 +1646,11 @@ describe('test/OSSObject.test.ts', () => { mime: 'image/png', }); - let result = await ossObject.get(imageName, { process: 'image/resize,w_200' }); + let result = await ossObject.get(imageName, { + process: 'image/resize,w_200', + }); assert.equal(result.res.status, 200); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); // assert.deepEqual(result.content == fs.readFileSync(processedImagePath), // 'get content should be same as test/nodejs-processed-w200.png'); @@ -1454,32 +1661,37 @@ describe('test/OSSObject.test.ts', () => { subres: { 'x-oss-process': 'image/resize,w_100' }, }); assert.equal(result.res.status, 200); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); }); it('should throw NoSuchKeyError when object not exists', async () => { - await assert.rejects(async () => { - await ossObject.get('not-exists-key'); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - assert.equal(typeof err.requestId, 'string'); - assert.match(err.message, /The specified key does not exist\./); - return true; - }); + await assert.rejects( + async () => { + await ossObject.get('not-exists-key'); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + assert.equal(typeof err.requestId, 'string'); + assert.match(err.message, /The specified key does not exist\./); + return true; + } + ); }); describe('If-Modified-Since header', () => { it('should 200 when If-Modified-Since < object modified time', async () => { - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); const result = await ossObject.get(name, { headers: { 'If-Modified-Since': lastYear.toUTCString(), }, }); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); - assert(result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok( + result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0 + ); assert.equal(result.res.status, 200); }); @@ -1489,20 +1701,20 @@ describe('test/OSSObject.test.ts', () => { 'If-Modified-Since': resHeaders.date, }, }); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); assert.equal(result.content.length, 0); assert.equal(result.res.status, 304); }); it('should 304 when If-Modified-Since > object modified time', async () => { - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const result = await ossObject.get(name, { headers: { 'If-Modified-Since': nextYear.toUTCString(), }, }); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); assert.equal(result.content.length, 0); assert.equal(result.res.status, 304); }); @@ -1510,23 +1722,28 @@ describe('test/OSSObject.test.ts', () => { describe('If-Unmodified-Since header', () => { it('should throw PreconditionFailedError when If-Unmodified-Since < object modified time', async () => { - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); - await assert.rejects(async () => { - await ossObject.get(name, { - headers: { - 'If-Unmodified-Since': lastYear.toUTCString(), - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.status, 412); - assert.equal(err.code, 'PreconditionFailed'); - assert.match(err.message, - /At least one of the pre-conditions you specified did not hold. \(condition=If-Unmodified-Since\)/); - assert.equal(typeof err.requestId, 'string'); - assert.equal(typeof err.hostId, 'string'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.get(name, { + headers: { + 'If-Unmodified-Since': lastYear.toUTCString(), + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.status, 412); + assert.equal(err.code, 'PreconditionFailed'); + assert.match( + err.message, + /At least one of the pre-conditions you specified did not hold. \(condition=If-Unmodified-Since\)/ + ); + assert.equal(typeof err.requestId, 'string'); + assert.equal(typeof err.hostId, 'string'); + return true; + } + ); }); it('should 200 when If-Unmodified-Since = object modified time', async () => { @@ -1536,12 +1753,14 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(result.res.status, 200); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); - assert(result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok( + result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0 + ); }); it('should 200 when If-Unmodified-Since > object modified time', async () => { - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const result = await ossObject.get(name, { headers: { @@ -1549,8 +1768,10 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(result.res.status, 200); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); - assert(result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok( + result.content.toString().indexOf('oss-client/oss/get-meta.js') > 0 + ); }); }); @@ -1565,17 +1786,20 @@ describe('test/OSSObject.test.ts', () => { }); it('should throw PreconditionFailedError when If-Match not equal object etag', async () => { - await assert.rejects(async () => { - await ossObject.get(name, { - headers: { - 'If-Match': 'foo', - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.code, 'PreconditionFailed'); - assert.equal(err.status, 412); - return true; - }); + await assert.rejects( + async () => { + await ossObject.get(name, { + headers: { + 'If-Match': 'foo', + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'PreconditionFailed'); + assert.equal(err.status, 412); + return true; + } + ); }); }); @@ -1610,7 +1834,7 @@ describe('test/OSSObject.test.ts', () => { }, }); assert.equal(result.res.headers['content-length'], '10'); - assert(Buffer.isBuffer(result.content), 'content should be Buffer'); + assert.ok(Buffer.isBuffer(result.content), 'content should be Buffer'); assert.equal(result.content.toString(), 'aaaaaaaaaa'); }); }); @@ -1618,7 +1842,7 @@ describe('test/OSSObject.test.ts', () => { describe('getStream()', () => { let name: string; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/get-stream.js`; await ossObject.put(name, __filename, { meta: { @@ -1629,18 +1853,19 @@ describe('test/OSSObject.test.ts', () => { }); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); it('should get exists object stream', async () => { const result = await ossObject.getStream(name); assert.equal(result.res.status, 200); - assert(result.stream instanceof Readable); + assert.ok(result.stream instanceof Readable); const tmpfile = path.join(tmpdir, 'get-stream.js'); const tmpStream = createWriteStream(tmpfile); function finish() { + // oxlint-disable-next-line avoid-new return new Promise(resolve => { tmpStream.on('finish', () => { resolve(); @@ -1650,7 +1875,10 @@ describe('test/OSSObject.test.ts', () => { result.stream.pipe(tmpStream); await finish(); - assert.equal(readFileSync(tmpfile, 'utf8'), readFileSync(__filename, 'utf8')); + assert.equal( + readFileSync(tmpfile, 'utf8'), + readFileSync(__filename, 'utf8') + ); }); /** @@ -1665,8 +1893,12 @@ describe('test/OSSObject.test.ts', () => { mime: 'image/png', }); - let result = await ossObject.getStream(imageName, { process: 'image/resize,w_200' }); - let result2 = await ossObject.getStream(imageName, { process: 'image/resize,w_200' }); + let result = await ossObject.getStream(imageName, { + process: 'image/resize,w_200', + }); + let result2 = await ossObject.getStream(imageName, { + process: 'image/resize,w_200', + }); assert.equal(result.res.status, 200); assert.equal(result2.res.status, 200); result = await ossObject.getStream(imageName, { @@ -1682,12 +1914,15 @@ describe('test/OSSObject.test.ts', () => { }); it('should throw error when object not exists', async () => { - await assert.rejects(async () => { - await ossObject.getStream(`${name}not-exists`); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.getStream(`${name}not-exists`); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + return true; + } + ); }); }); @@ -1695,27 +1930,31 @@ describe('test/OSSObject.test.ts', () => { let name: string; let resHeaders: IncomingHttpHeaders; let fileSize: number; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/object-meta.js`; const object = await ossObject.put(name, __filename); - fileSize = (await stat(__filename)).size; + const { size } = await stat(__filename); assert.equal(typeof object.res.headers['x-oss-request-id'], 'string'); resHeaders = object.res.headers; + fileSize = size; }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); it('should head not exists object throw NoSuchKeyError', async () => { - await assert.rejects(async () => { - await ossObject.getObjectMeta(`${name}not-exists`); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.equal(err.status, 404); - assert.equal(typeof err.requestId, 'string'); - return true; - }); + await assert.rejects( + async () => { + await ossObject.getObjectMeta(`${name}not-exists`); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.equal(err.status, 404); + assert.equal(typeof err.requestId, 'string'); + return true; + } + ); }); it('should return Etag and Content-Length', async () => { @@ -1724,7 +1963,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(info.res.headers.etag, resHeaders.etag); assert.equal(info.res.headers['content-length'], fileSize.toString()); // no versionId won't return this header - assert(!info.res.headers['x-oss-last-access-time']); + assert.ok(!info.res.headers['x-oss-last-access-time']); }); }); @@ -1733,7 +1972,7 @@ describe('test/OSSObject.test.ts', () => { let resHeaders: IncomingHttpHeaders; // let otherBucket: string; // let otherBucketObject: string; - before(async () => { + beforeAll(async () => { name = `${prefix}oss-client/oss/copy-meta.js`; const object = await ossObject.put(name, __filename, { meta: { @@ -1754,7 +1993,7 @@ describe('test/OSSObject.test.ts', () => { // store.useBucket(bucket); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); @@ -1837,20 +2076,17 @@ describe('test/OSSObject.test.ts', () => { // slus: 'test2.html', // }, // }); - // let info = await ossObject.head(sourceName); // assert.equal(info.meta.uid, '3'); // assert.equal(info.meta.pid, '12345'); // assert.equal(info.meta.slus, 'test2.html'); // assert.equal(info.status, 200); - // sourceName = `/${bucket}/${sourceName}`; // const originname = `${prefix}oss-client/oss/copy-new_测试2.js`; // result = await ossObject.copy(originname, sourceName); // assert.equal(result.res.status, 200); // assert.equal(typeof result.data.etag, 'string'); // assert.equal(typeof result.data.lastModified, 'string'); - // info = await ossObject.head(originname); // assert.equal(info.meta.uid, '3'); // assert.equal(info.meta.pid, '12345'); @@ -1871,8 +2107,8 @@ describe('test/OSSObject.test.ts', () => { const info = await ossObject.head(targetName); assert.equal(info.meta.uid, '2'); - assert(!info.meta.pid); - assert(!info.meta.slus); + assert.ok(!info.meta.pid); + assert.ok(!info.meta.slus); assert.equal(info.status, 200); }); @@ -1890,7 +2126,7 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof result.data?.etag, 'string'); assert.equal(typeof result.data?.lastModified, 'string'); let info = await ossObject.head(targetName); - assert(!info.res.headers['cache-control']); + assert.ok(!info.res.headers['cache-control']); // add Cache-Control header to a exists object result = await ossObject.copy(targetName, targetName, { @@ -1902,37 +2138,46 @@ describe('test/OSSObject.test.ts', () => { assert.equal(typeof result.data?.etag, 'string'); assert.equal(typeof result.data?.lastModified, 'string'); info = await ossObject.head(targetName); - assert.equal(info.res.headers['cache-control'], 'max-age=0, s-maxage=86400'); + assert.equal( + info.res.headers['cache-control'], + 'max-age=0, s-maxage=86400' + ); }); it('should throw NoSuchKeyError when source object not exists', async () => { - await assert.rejects(async () => { - await ossObject.copy('new-object', 'not-exists-object'); - }, (err: OSSClientError) => { - assert.equal(err.code, 'NoSuchKey'); - assert.match(err.message, /The specified key does not exist\./); - assert.equal(err.status, 404); - return true; - }); + await assert.rejects( + async () => { + await ossObject.copy('new-object', 'not-exists-object'); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'NoSuchKey'); + assert.match(err.message, /The specified key does not exist\./); + assert.equal(err.status, 404); + return true; + } + ); }); describe('If-Match header', () => { it('should throw PreconditionFailedError when If-Match not equal source object etag', async () => { - await assert.rejects(async () => { - await ossObject.copy('new-name', name, { - headers: { - 'If-Match': 'foo-bar', - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.code, 'PreconditionFailed'); - assert.match( - err.message, - /At least one of the pre-conditions you specified did not hold. \(condition=If-Match\)/, - ); - assert.equal(err.status, 412); - return true; - }); + await assert.rejects( + async () => { + await ossObject.copy('new-name', name, { + headers: { + 'If-Match': 'foo-bar', + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'PreconditionFailed'); + assert.match( + err.message, + /At least one of the pre-conditions you specified did not hold. \(condition=If-Match\)/ + ); + assert.equal(err.status, 412); + return true; + } + ); }); it('should copy object when If-Match equal source object etag', async () => { @@ -1975,7 +2220,7 @@ describe('test/OSSObject.test.ts', () => { describe('If-Modified-Since header', () => { it('should 304 when If-Modified-Since > source object modified time', async () => { const targetName = `${prefix}oss-client/oss/copy-new-If-Modified-Since.js`; - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const result = await ossObject.copy(targetName, name, { headers: { @@ -1997,7 +2242,7 @@ describe('test/OSSObject.test.ts', () => { it('should 200 when If-Modified-Since < source object modified time', async () => { const targetName = `${prefix}oss-client/oss/copy-new-If-Modified-Since.js`; - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); const result = await ossObject.copy(targetName, name, { headers: { @@ -2011,7 +2256,7 @@ describe('test/OSSObject.test.ts', () => { describe('If-Unmodified-Since header', () => { it('should 200 when If-Unmodified-Since > source object modified time', async () => { const targetName = `${prefix}oss-client/oss/copy-new-If-Unmodified-Since.js`; - const nextYear = new Date(resHeaders.date!); + const nextYear = new Date(resHeaders.date as string); nextYear.setFullYear(nextYear.getFullYear() + 1); const result = await ossObject.copy(targetName, name, { headers: { @@ -2033,23 +2278,26 @@ describe('test/OSSObject.test.ts', () => { it('should throw PreconditionFailedError when If-Unmodified-Since < source object modified time', async () => { const targetName = `${prefix}oss-client/oss/copy-new-If-Unmodified-Since.js`; - const lastYear = new Date(resHeaders.date!); + const lastYear = new Date(resHeaders.date as string); lastYear.setFullYear(lastYear.getFullYear() - 1); - await assert.rejects(async () => { - await ossObject.copy(targetName, name, { - headers: { - 'If-Unmodified-Since': lastYear.toUTCString(), - }, - }); - }, (err: OSSClientError) => { - assert.equal(err.code, 'PreconditionFailed'); - assert.match( - err.message, - /At least one of the pre-conditions you specified did not hold. \(condition=If-Unmodified-Since\)/, - ); - assert.equal(err.status, 412); - return true; - }); + await assert.rejects( + async () => { + await ossObject.copy(targetName, name, { + headers: { + 'If-Unmodified-Since': lastYear.toUTCString(), + }, + }); + }, + (err: OSSClientError) => { + assert.equal(err.code, 'PreconditionFailed'); + assert.match( + err.message, + /At least one of the pre-conditions you specified did not hold. \(condition=If-Unmodified-Since\)/ + ); + assert.equal(err.status, 412); + return true; + } + ); }); }); }); @@ -2091,11 +2339,11 @@ describe('test/OSSObject.test.ts', () => { describe('getObjectTagging() putObjectTagging() deleteObjectTagging()', () => { const name = `${prefix}oss-client/tagging-${Date.now()}.js`; - before(async () => { + beforeAll(async () => { await ossObject.put(name, __filename); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); }); @@ -2124,95 +2372,140 @@ describe('test/OSSObject.test.ts', () => { }); it('maximum of 10 tags for a object', async () => { - await assert.rejects(async () => { - const tag: any = {}; - Array(11) - .fill(1) - .forEach((_, index) => { - tag[index] = index; - }); - await ossObject.putObjectTagging(name, tag); - }, (err: TypeError) => { - assert.strictEqual('maximum of 10 tags for a object', err.message); - return true; - }); + await assert.rejects( + async () => { + const tags: Record = {}; + for (let i = 0; i < 11; i++) { + tags[`key${i}`] = `value${i}`; + } + await ossObject.putObjectTagging(name, tags); + }, + (err: TypeError) => { + assert.strictEqual('maximum of 10 tags for a object', err.message); + return true; + } + ); }); it('tag can contain invalid string', async () => { - await assert.rejects(async () => { - const errorStr = '错误字符串@#¥%……&*!'; - const key = errorStr; - const value = errorStr; - const tag = { [key]: value }; - await ossObject.putObjectTagging(name, tag); - }, (err: TypeError) => { - assert.strictEqual( - 'tag can contain letters, numbers, spaces, and the following symbols: plus sign (+), hyphen (-), equal sign (=), period (.), underscore (_), colon (:), and forward slash (/)', - err.message); - return true; - }); + await assert.rejects( + async () => { + const errorStr = '错误字符串@#¥%……&*!'; + const key = errorStr; + const value = errorStr; + const tag = { [key]: value }; + await ossObject.putObjectTagging(name, tag); + }, + (err: TypeError) => { + assert.strictEqual( + 'tag can contain letters, numbers, spaces, and the following symbols: plus sign (+), hyphen (-), equal sign (=), period (.), underscore (_), colon (:), and forward slash (/)', + err.message + ); + return true; + } + ); }); it('tag key can be a maximum of 128 bytes in length', async () => { - await assert.rejects(async () => { - const key = new Array(129).fill('1').join(''); - const tag = { [key]: '1' }; - await ossObject.putObjectTagging(name, tag); - }, (err: TypeError) => { - assert.strictEqual('tag key can be a minimum of 1 byte and a maximum of 128 bytes in length', err.message); - return true; - }); + await assert.rejects( + async () => { + const key = Array.from({ length: 129 }, () => '1').join(''); + const tag = { [key]: '1' }; + await ossObject.putObjectTagging(name, tag); + }, + (err: TypeError) => { + assert.strictEqual( + 'tag key can be a minimum of 1 byte and a maximum of 128 bytes in length', + err.message + ); + return true; + } + ); }); it('tag value can be a maximum of 256 bytes in length', async () => { - await assert.rejects(async () => { - const value = new Array(257).fill('1').join(''); - const tag = { a: value }; - await ossObject.putObjectTagging(name, tag); - }, (err: TypeError) => { - assert.strictEqual('tag value can be a maximum of 256 bytes in length', err.message); - return true; - }); + await assert.rejects( + async () => { + const value = Array.from({ length: 257 }, () => '1').join(''); + const tag = { a: value }; + await ossObject.putObjectTagging(name, tag); + }, + (err: TypeError) => { + assert.strictEqual( + 'tag value can be a maximum of 256 bytes in length', + err.message + ); + return true; + } + ); }); it('should throw error when the type of tag is not Object', async () => { - await assert.rejects(async () => { - const tag = [{ a: 1 }]; - await ossObject.putObjectTagging(name, tag as any); - }, (err: TypeError) => { - assert.equal(err.message, 'the key and value of the tag must be String'); - return true; - }); + await assert.rejects( + async () => { + const tag = [{ a: 1 }]; + // oxlint-disable-next-line @typescript-eslint/no-explicit-any + await ossObject.putObjectTagging(name, tag as any); + }, + (err: TypeError) => { + assert.equal( + err.message, + 'the key and value of the tag must be String' + ); + return true; + } + ); }); it('should throw error when the type of tag value is number', async () => { - await assert.rejects(async () => { - const tag = { a: 1 }; - await ossObject.putObjectTagging(name, tag as any); - }, (err: TypeError) => { - assert.strictEqual('the key and value of the tag must be String', err.message); - return true; - }); + await assert.rejects( + async () => { + const tag = { a: 1 }; + // oxlint-disable-next-line @typescript-eslint/no-explicit-any + await ossObject.putObjectTagging(name, tag as any); + }, + (err: TypeError) => { + assert.strictEqual( + 'the key and value of the tag must be String', + err.message + ); + return true; + } + ); }); it('should throw error when the type of tag value is Object', async () => { - await assert.rejects(async () => { - const tag = { a: { inner: '1' } }; - await ossObject.putObjectTagging(name, tag as any); - }, (err: TypeError) => { - assert.strictEqual('the key and value of the tag must be String', err.message); - return true; - }); + await assert.rejects( + async () => { + const tag = { a: { inner: '1' } }; + // oxlint-disable-next-line @typescript-eslint/no-explicit-any + await ossObject.putObjectTagging(name, tag as any); + }, + (err: TypeError) => { + assert.strictEqual( + 'the key and value of the tag must be String', + err.message + ); + return true; + } + ); }); it('should throw error when the type of tag value is Array', async () => { - await assert.rejects(async () => { - const tag = { a: [ '1', '2' ] }; - await ossObject.putObjectTagging(name, tag as any); - }, (err: TypeError) => { - assert.strictEqual('the key and value of the tag must be String', err.message); - return true; - }); + await assert.rejects( + async () => { + const tag = { a: ['1', '2'] }; + // oxlint-disable-next-line @typescript-eslint/no-explicit-any + await ossObject.putObjectTagging(name, tag as any); + }, + (err: TypeError) => { + assert.strictEqual( + 'the key and value of the tag must be String', + err.message + ); + return true; + } + ); }); it('should delete the tags of object', async () => { @@ -2242,7 +2535,7 @@ describe('test/OSSObject.test.ts', () => { const params = ossObject.calculatePostSignature(policy); assert.equal(typeof params.policy, 'string'); - const result = await urllib.request(url, { + const result = await request(url, { method: 'POST', data: { ...params, @@ -2262,7 +2555,7 @@ describe('test/OSSObject.test.ts', () => { ossObject.calculatePostSignature('string'); }, /Policy string is not a valid JSON/); assert.throws(() => { - ossObject.calculatePostSignature(123 as any); + ossObject.calculatePostSignature(123 as unknown as object); }, /policy must be JSON string or Object/); }); }); @@ -2271,14 +2564,17 @@ describe('test/OSSObject.test.ts', () => { it('should return object url', () => { let name = 'test.js'; let url = ossObject.generateObjectUrl(name); - assert(url); + assert.ok(url); name = '/foo/bar/a%2Faa/test&+-123~!.js'; url = ossObject.generateObjectUrl(name, 'https://foo.com'); assert.equal(url, 'https://foo.com/foo/bar/a%252Faa/test%26%2B-123~!.js'); assert.equal(ossObject.generateObjectUrl(name, 'https://foo.com'), url); const url2 = ossObject.generateObjectUrl(name, 'https://foo.com/'); - assert.equal(url2, 'https://foo.com/foo/bar/a%252Faa/test%26%2B-123~!.js'); + assert.equal( + url2, + 'https://foo.com/foo/bar/a%252Faa/test%26%2B-123~!.js' + ); assert.equal(ossObject.generateObjectUrl(name, 'https://foo.com/'), url2); }); }); @@ -2286,12 +2582,12 @@ describe('test/OSSObject.test.ts', () => { describe('processObjectSave()', () => { const name = `${prefix}oss-client/processObjectSave/sourceObject.png`; const target = `${prefix}oss-client/processObjectSave/processObject_target${Date.now()}.jpg`; - before(async () => { + beforeAll(async () => { const imagePath = path.join(__dirname, 'nodejs-1024x768.png'); await ossObject.put(name, imagePath); }); - after(async () => { + afterAll(async () => { await ossObject.delete(name); await ossObject.delete(target); }); @@ -2300,7 +2596,7 @@ describe('test/OSSObject.test.ts', () => { const result = await ossObject.processObjectSave( name, target, - 'image/watermark,text_aGVsbG8g5Zu+54mH5pyN5Yqh77yB,color_ff6a00,', + 'image/watermark,text_aGVsbG8g5Zu+54mH5pyN5Yqh77yB,color_ff6a00,' ); assert.equal(result.res.status, 200); assert.equal(result.status, 200); @@ -2320,6 +2616,7 @@ describe('test/OSSObject.test.ts', () => { it('should throw error when bucket empty', async () => { assert.throws(() => { + // oxlint-disable-next-line no-new new OSSObject({ ...config.oss, bucket: '', diff --git a/test/config.ts b/test/config.ts index caa2051ce..b7cb2eb5e 100644 --- a/test/config.ts +++ b/test/config.ts @@ -1,11 +1,17 @@ +import { env } from 'read-env-value'; + export default { - prefix: `${process.platform}-${process.version}-${new Date().getTime()}/`, + prefix: `${process.platform}-${process.version}-${Date.now()}/`, oss: { - accessKeyId: process.env.OSS_CLIENT_ID!, - accessKeySecret: process.env.OSS_CLIENT_SECRET!, - region: process.env.OSS_CLIENT_REGION!, - endpoint: process.env.OSS_CLIENT_ENDPOINT!, - bucket: process.env.OSS_CLIENT_BUCKET!, + accessKeyId: env('OSS_CLIENT_ID', 'string', ''), + accessKeySecret: env('OSS_CLIENT_SECRET', 'string', ''), + region: env('OSS_CLIENT_REGION', 'string', 'oss-cn-hangzhou'), + endpoint: env( + 'OSS_CLIENT_ENDPOINT', + 'string', + 'https://oss-cn-hangzhou.aliyuncs.com' + ), + bucket: env('OSS_CLIENT_BUCKET', 'string', 'oss-client-test'), }, timeout: '120s', }; diff --git a/test/util/isIP.test.ts b/test/util/isIP.test.ts index 9c8c1a963..6b3f13556 100644 --- a/test/util/isIP.test.ts +++ b/test/util/isIP.test.ts @@ -1,4 +1,7 @@ import { strict as assert } from 'node:assert'; + +import { describe, it } from 'vitest'; + import { isIP } from '../../src/util/index.js'; describe('test/util/isIP.test.ts', () => {