diff --git a/libraries/botbuilder-azure/eslint.config.cjs b/libraries/botbuilder-azure/eslint.config.cjs deleted file mode 100644 index 63647b52cc..0000000000 --- a/libraries/botbuilder-azure/eslint.config.cjs +++ /dev/null @@ -1,10 +0,0 @@ -const onlyWarn = require("eslint-plugin-only-warn"); -const sharedConfig = require("../../eslint.config.cjs") - -module.exports = [ - ...sharedConfig, - { - plugins: { - "only-warn": onlyWarn, - }, - }]; diff --git a/libraries/botbuilder-azure/package.json b/libraries/botbuilder-azure/package.json index 6cb5aec105..57cb715a4b 100644 --- a/libraries/botbuilder-azure/package.json +++ b/libraries/botbuilder-azure/package.json @@ -34,7 +34,6 @@ "bcryptjs": "^2.4.3", "botbuilder": "4.1.6", "botbuilder-stdlib": "4.1.6", - "eslint-plugin-only-warn": "^1.1.0", "lodash": "^4.17.20", "p-map": "^4.0.0" }, @@ -49,7 +48,7 @@ "build:rollup": "yarn clean && yarn build && api-extractor run --verbose --local", "clean": "rimraf _ts3.4 lib tsconfig.tsbuildinfo", "depcheck": "depcheck --config ../../.depcheckrc", - "lint": "eslint .", + "lint": "eslint . --config ../../eslint.config.cjs", "postbuild": "downlevel-dts lib _ts3.4/lib --checksum", "test": "yarn build && nyc mocha tests/", "test:compat": "api-extractor run --verbose" diff --git a/libraries/botbuilder-azure/src/azureBlobTranscriptStore.ts b/libraries/botbuilder-azure/src/azureBlobTranscriptStore.ts index 5b4ae52097..c6fb1879ef 100644 --- a/libraries/botbuilder-azure/src/azureBlobTranscriptStore.ts +++ b/libraries/botbuilder-azure/src/azureBlobTranscriptStore.ts @@ -84,7 +84,7 @@ export class AzureBlobTranscriptStore implements TranscriptStore { this.containerClient = new ContainerClient( this.settings.storageAccountOrConnectionString, this.settings.containerName, - pipeline.options + pipeline.options, ); } @@ -137,7 +137,7 @@ export class AzureBlobTranscriptStore implements TranscriptStore { channelId: string, conversationId: string, continuationToken?: string, - startDate?: Date + startDate?: Date, ): Promise> { if (!channelId) { throw new Error('Missing channelId'); @@ -174,7 +174,7 @@ export class AzureBlobTranscriptStore implements TranscriptStore { const fromIdx = startDate != null ? blobItems.findIndex( - (blobItem) => blobItem?.properties?.createdOn && blobItem?.properties?.createdOn >= startDate + (blobItem) => blobItem?.properties?.createdOn && blobItem?.properties?.createdOn >= startDate, ) : 0; @@ -193,7 +193,7 @@ export class AzureBlobTranscriptStore implements TranscriptStore { const activity = (await StreamConsumers.json(readableStreamBody)) as any; return { ...activity, timestamp: new Date(activity.timestamp) } as Activity; }, - { concurrency: this.concurrency } + { concurrency: this.concurrency }, ); activities.forEach((activity) => { diff --git a/libraries/botbuilder-azure/src/blobStorage.ts b/libraries/botbuilder-azure/src/blobStorage.ts index 31ea7c31e5..5fe692b7cc 100644 --- a/libraries/botbuilder-azure/src/blobStorage.ts +++ b/libraries/botbuilder-azure/src/blobStorage.ts @@ -90,7 +90,7 @@ const ResolvePromisesSerial = (values, promise) => .map((value) => () => promise(value)) .reduce( (promise, func) => promise.then((result) => func().then(Array.prototype.concat.bind(result))), - Promise.resolve([]) + Promise.resolve([]), ); /** @@ -155,7 +155,7 @@ export class BlobStorage implements Storage { this.containerClient = new ContainerClient( this.settings.storageAccountOrConnectionString, this.settings.containerName, - pipeline.options + pipeline.options, ); this.useEmulator = settings.storageAccountOrConnectionString === 'UseDevelopmentStorage=true;'; @@ -194,7 +194,7 @@ export class BlobStorage implements Storage { // If blob does not exist, return an empty DocumentStoreItem. return { document: {} } as DocumentStoreItem; } - }) + }), ) .then((items: DocumentStoreItem[]) => { if (items !== null && items.length > 0) { @@ -257,7 +257,7 @@ export class BlobStorage implements Storage { // depending on the payload's size. The default maximum size for a single blob upload is 128MB. // An 'InvalidBlockList' error is commonly caused due to concurrently uploading an object larger than 128MB in size. const promise: (b: any) => Promise = ( - blob: any + blob: any, ): Promise => { const blockBlobClient = this.containerClient.getBlockBlobClient(blob.id); const uploadBlobResponse = blockBlobClient.upload(blob.data, blob.data.length, blob.options); @@ -301,7 +301,7 @@ export class BlobStorage implements Storage { sanitizedKeys.map(async (key: string) => { const blockBlobClient = this.containerClient.getBlockBlobClient(key); return await blockBlobClient.deleteIfExists(); - }) + }), ); }) .then(() => { @@ -328,7 +328,7 @@ export class BlobStorage implements Storage { // The number of path segments comprising the blob name cannot exceed 254 const validKey: string = segments.reduce( (acc: any, curr: any, index: number) => [acc, curr].join(index < 255 ? '/' : ''), - base + base, ); // Reserved URL characters must be escaped. diff --git a/libraries/botbuilder-azure/src/cosmosDbKeyEscape.ts b/libraries/botbuilder-azure/src/cosmosDbKeyEscape.ts index bd48d50e71..ed2c5f2ff6 100644 --- a/libraries/botbuilder-azure/src/cosmosDbKeyEscape.ts +++ b/libraries/botbuilder-azure/src/cosmosDbKeyEscape.ts @@ -21,7 +21,7 @@ export namespace CosmosDbKeyEscape { return map; }, - new Map() + new Map(), ); /** @@ -43,7 +43,7 @@ export namespace CosmosDbKeyEscape { const keySplitted: string[] = key.split(''); const firstIllegalCharIndex: number = keySplitted.findIndex((c: string): boolean => - illegalKeys.some((i: string) => i === c) + illegalKeys.some((i: string) => i === c), ); // If there are no illegal characters return immediately and avoid any further processing/allocations @@ -54,7 +54,7 @@ export namespace CosmosDbKeyEscape { const sanitizedKey = keySplitted.reduce( (result: string, c: string) => result + (illegalKeyCharacterReplacementMap.has(c) ? illegalKeyCharacterReplacementMap.get(c) : c), - '' + '', ); return truncateKey(`${sanitizedKey}${keySuffix || ''}`, compatibilityMode); diff --git a/libraries/botbuilder-azure/src/cosmosDbPartitionedStorage.ts b/libraries/botbuilder-azure/src/cosmosDbPartitionedStorage.ts index f49cea76fc..b1384130dd 100644 --- a/libraries/botbuilder-azure/src/cosmosDbPartitionedStorage.ts +++ b/libraries/botbuilder-azure/src/cosmosDbPartitionedStorage.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-types */ /** * @module botbuilder-azure */ @@ -13,7 +12,7 @@ import { DoOnce } from './doOnce'; import { Storage, StoreItems } from 'botbuilder'; import { TokenCredential } from '@azure/core-auth'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const pjson: Record<'name' | 'version', string> = require('../package.json'); const _doOnce: DoOnce = new DoOnce(); @@ -173,7 +172,7 @@ export class CosmosDbPartitionedStorage implements Storage { const suffixEscaped = CosmosDbKeyEscape.escapeKey(cosmosDbStorageOptions.keySuffix); if (cosmosDbStorageOptions.keySuffix !== suffixEscaped) { throw new ReferenceError( - `Cannot use invalid Row Key characters: ${cosmosDbStorageOptions.keySuffix} in keySuffix` + `Cannot use invalid Row Key characters: ${cosmosDbStorageOptions.keySuffix} in keySuffix`, ); } } @@ -202,43 +201,41 @@ export class CosmosDbPartitionedStorage implements Storage { const storeItems: StoreItems = {}; await Promise.all( - keys.map( - async (k: string): Promise => { - try { - const escapedKey = CosmosDbKeyEscape.escapeKey( - k, - this.cosmosDbStorageOptions.keySuffix, - this.cosmosDbStorageOptions.compatibilityMode - ); + keys.map(async (k: string): Promise => { + try { + const escapedKey = CosmosDbKeyEscape.escapeKey( + k, + this.cosmosDbStorageOptions.keySuffix, + this.cosmosDbStorageOptions.compatibilityMode, + ); - const readItemResponse = await this.container - .item(escapedKey, this.getPartitionKey(escapedKey)) - .read(); - const documentStoreItem = readItemResponse.resource; - if (documentStoreItem) { - storeItems[documentStoreItem.realId] = documentStoreItem.document; - storeItems[documentStoreItem.realId].eTag = documentStoreItem._etag; - } - } catch (err) { - // When an item is not found a CosmosException is thrown, but we want to - // return an empty collection so in this instance we catch and do not rethrow. - // Throw for any other exception. - if (err.code === 404) { - // no-op - } - // Throw unique error for 400s - else if (err.code === 400) { - this.throwInformativeError( - `Error reading from container. You might be attempting to read from a non-partitioned + const readItemResponse = await this.container + .item(escapedKey, this.getPartitionKey(escapedKey)) + .read(); + const documentStoreItem = readItemResponse.resource; + if (documentStoreItem) { + storeItems[documentStoreItem.realId] = documentStoreItem.document; + storeItems[documentStoreItem.realId].eTag = documentStoreItem._etag; + } + } catch (err) { + // When an item is not found a CosmosException is thrown, but we want to + // return an empty collection so in this instance we catch and do not rethrow. + // Throw for any other exception. + if (err.code === 404) { + // no-op + } + // Throw unique error for 400s + else if (err.code === 400) { + this.throwInformativeError( + `Error reading from container. You might be attempting to read from a non-partitioned container or a container that does not use '/id' as the partitionKeyPath`, - err - ); - } else { - this.throwInformativeError('Error reading from container', err); - } + err, + ); + } else { + this.throwInformativeError('Error reading from container', err); } } - ) + }), ); return storeItems; @@ -259,38 +256,36 @@ export class CosmosDbPartitionedStorage implements Storage { await this.initialize(); await Promise.all( - Object.entries(changes).map( - async ([key, { eTag, ...change }]): Promise => { - const document = new DocumentStoreItem({ - id: CosmosDbKeyEscape.escapeKey( - key, - this.cosmosDbStorageOptions.keySuffix, - this.cosmosDbStorageOptions.compatibilityMode - ), - realId: key, - document: change, - }); + Object.entries(changes).map(async ([key, { eTag, ...change }]): Promise => { + const document = new DocumentStoreItem({ + id: CosmosDbKeyEscape.escapeKey( + key, + this.cosmosDbStorageOptions.keySuffix, + this.cosmosDbStorageOptions.compatibilityMode, + ), + realId: key, + document: change, + }); - const accessCondition = - eTag !== '*' && eTag != null && eTag.length > 0 - ? { accessCondition: { type: 'IfMatch', condition: eTag } } - : undefined; - - try { - await this.container.items.upsert(document, accessCondition); - } catch (err) { - // This check could potentially be performed before even attempting to upsert the item - // so that a request wouldn't be made to Cosmos if it's expected to fail. - // However, performing the check here ensures that this custom exception is only thrown - // if Cosmos returns an error first. - // This way, the nesting limit is not imposed on the Bot Framework side - // and no exception will be thrown if the limit is eventually changed on the Cosmos side. - this.checkForNestingError(change, err); - - this.throwInformativeError('Error upserting document', err); - } + const accessCondition = + eTag !== '*' && eTag != null && eTag.length > 0 + ? { accessCondition: { type: 'IfMatch', condition: eTag } } + : undefined; + + try { + await this.container.items.upsert(document, accessCondition); + } catch (err) { + // This check could potentially be performed before even attempting to upsert the item + // so that a request wouldn't be made to Cosmos if it's expected to fail. + // However, performing the check here ensures that this custom exception is only thrown + // if Cosmos returns an error first. + // This way, the nesting limit is not imposed on the Bot Framework side + // and no exception will be thrown if the limit is eventually changed on the Cosmos side. + this.checkForNestingError(change, err); + + this.throwInformativeError('Error upserting document', err); } - ) + }), ); } @@ -303,25 +298,23 @@ export class CosmosDbPartitionedStorage implements Storage { await this.initialize(); await Promise.all( - keys.map( - async (k: string): Promise => { - const escapedKey = CosmosDbKeyEscape.escapeKey( - k, - this.cosmosDbStorageOptions.keySuffix, - this.cosmosDbStorageOptions.compatibilityMode - ); - try { - await this.container.item(escapedKey, this.getPartitionKey(escapedKey)).delete(); - } catch (err) { - // If trying to delete a document that doesn't exist, do nothing. Otherwise, throw - if (err.code === 404) { - // no-op - } else { - this.throwInformativeError('Unable to delete document', err); - } + keys.map(async (k: string): Promise => { + const escapedKey = CosmosDbKeyEscape.escapeKey( + k, + this.cosmosDbStorageOptions.keySuffix, + this.cosmosDbStorageOptions.compatibilityMode, + ); + try { + await this.container.item(escapedKey, this.getPartitionKey(escapedKey)).delete(); + } catch (err) { + // If trying to delete a document that doesn't exist, do nothing. Otherwise, throw + if (err.code === 404) { + // no-op + } else { + this.throwInformativeError('Unable to delete document', err); } } - ) + }), ); } @@ -350,7 +343,7 @@ export class CosmosDbPartitionedStorage implements Storage { const dbAndContainerKey = `${this.cosmosDbStorageOptions.databaseId}-${this.cosmosDbStorageOptions.containerId}`; this.container = await _doOnce.waitFor( dbAndContainerKey, - async (): Promise => await this.getOrCreateContainer() + async (): Promise => await this.getOrCreateContainer(), ); } } @@ -372,7 +365,7 @@ export class CosmosDbPartitionedStorage implements Storage { } else if (paths.indexOf(DocumentStoreItem.partitionKeyPath) === -1) { // We are not supporting custom Partition Key Paths. new Error( - `Custom Partition Key Paths are not supported. ${this.cosmosDbStorageOptions.containerId} has a custom Partition Key Path of ${paths[0]}.` + `Custom Partition Key Paths are not supported. ${this.cosmosDbStorageOptions.containerId} has a custom Partition Key Path of ${paths[0]}.`, ); } } else { diff --git a/libraries/botbuilder-azure/tests/azureBlobTranscriptStore.test.js b/libraries/botbuilder-azure/tests/azureBlobTranscriptStore.test.js index bf39796b11..ab6ef68632 100644 --- a/libraries/botbuilder-azure/tests/azureBlobTranscriptStore.test.js +++ b/libraries/botbuilder-azure/tests/azureBlobTranscriptStore.test.js @@ -47,6 +47,7 @@ describe('The AzureBlobTranscriptStore', function () { let mockBlobClient; let mockContainer; let pagedAsyncIterableIterator; + beforeEach(function () { sandbox = sinon.createSandbox(); @@ -110,6 +111,7 @@ describe('The AzureBlobTranscriptStore', function () { storage = new AzureBlobTranscriptStore(getSettings()); storage.containerClient = mockContainer; }); + after(function () { // reset mock sandbox.restore(); @@ -127,14 +129,14 @@ describe('The AzureBlobTranscriptStore', function () { it('it is constructed with an invalid container name', function () { assert.throws( () => new AzureBlobTranscriptStore({ containerName: '$%^$@' }), - new Error('Invalid container name.') + new Error('Invalid container name.'), ); }); it('it is constructed without the storageAccountOrConnectionString in the settings', function () { assert.throws( () => new AzureBlobTranscriptStore({ ...getSettings(), storageAccountOrConnectionString: '' }), - new Error('The storageAccountOrConnectionString is required.') + new Error('The storageAccountOrConnectionString is required.'), ); }); diff --git a/libraries/botbuilder-azure/tests/blobStorage.test.js b/libraries/botbuilder-azure/tests/blobStorage.test.js index c642ec7f98..b52fa60001 100644 --- a/libraries/botbuilder-azure/tests/blobStorage.test.js +++ b/libraries/botbuilder-azure/tests/blobStorage.test.js @@ -32,7 +32,7 @@ const reset = async () => { const checkEmulator = () => { if (!fs.existsSync(emulatorPath)) { console.warn( - 'These tests require Azure Storage Emulator! go to https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator#get-the-storage-emulator to download and install.' + 'These tests require Azure Storage Emulator! go to https://docs.microsoft.com/en-us/azure/storage/common/storage-use-emulator#get-the-storage-emulator to download and install.', ); } return true; @@ -40,115 +40,123 @@ const checkEmulator = () => { const storage = new BlobStorage(getSettings()); -describe('BlobStorage - Constructor', function () { - before('cleanup', reset); - after('cleanup', reset); - - it('missing settings should throw error', function () { - assert.throws(() => new BlobStorage(), Error, 'constructor should have thrown error about missing settings.'); +describe('BlobStorage', function () { + describe('Constructor', function () { + before('cleanup', reset); + + after('cleanup', reset); + + it('missing settings should throw error', function () { + assert.throws( + () => new BlobStorage(), + Error, + 'constructor should have thrown error about missing settings.', + ); + }); + + it('Invalid container name should throw error', function () { + assert.throws( + () => new BlobStorage(getSettings('invalid--name')), + Error, + 'constructor should have thrown error about invalid container name.', + ); + }); }); - it('Invalid container name should throw error', function () { - assert.throws( - () => new BlobStorage(getSettings('invalid--name')), - Error, - 'constructor should have thrown error about invalid container name.' + describe('Base Storage Tests', function () { + before( + 'cleanup', + flow(() => reset, checkEmulator), ); - }); -}); -describe('BlobStorage - Base Storage Tests', function () { - before( - 'cleanup', - flow(() => reset, checkEmulator) - ); - after('cleanup', reset); + after('cleanup', reset); - it('return empty object when reading unknown key', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.returnEmptyObjectWhenReadingUnknownKey(storage); + it('return empty object when reading unknown key', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.returnEmptyObjectWhenReadingUnknownKey(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('throws when reading null keys', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.handleNullKeysWhenReading(storage); + it('throws when reading null keys', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.handleNullKeysWhenReading(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('throws when writing null keys', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.handleNullKeysWhenWriting(storage); + it('throws when writing null keys', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.handleNullKeysWhenWriting(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('does not throw when writing no items', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.doesNotThrowWhenWritingNoItems(storage); + it('does not throw when writing no items', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.doesNotThrowWhenWritingNoItems(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('create an object', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.createObject(storage); + it('create an object', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.createObject(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('handle crazy keys', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.handleCrazyKeys(storage); + it('handle crazy keys', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.handleCrazyKeys(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('update an object', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.updateObject(storage); + it('update an object', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.updateObject(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('delete an object', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.deleteObject(storage); + it('delete an object', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.deleteObject(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('does not throw when deleting an unknown object', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.deleteUnknownObject(storage); + it('does not throw when deleting an unknown object', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.deleteUnknownObject(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('performs batch operations', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.performBatchOperations(storage); + it('performs batch operations', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.performBatchOperations(storage); - assert.strictEqual(testRan, true); - nockDone(); - }); + assert.strictEqual(testRan, true); + nockDone(); + }); - it('proceeds through a waterfall dialog', async function () { - const { nockDone } = await usingNock(this.test, mode); - const testRan = await StorageBaseTests.proceedsThroughWaterfall(storage); + it('proceeds through a waterfall dialog', async function () { + const { nockDone } = await usingNock(this.test, mode); + const testRan = await StorageBaseTests.proceedsThroughWaterfall(storage); - assert.strictEqual(testRan, true); - nockDone(); + assert.strictEqual(testRan, true); + nockDone(); + }); }); }); diff --git a/libraries/botbuilder-azure/tests/cosmosDbKeyEscape.test.js b/libraries/botbuilder-azure/tests/cosmosDbKeyEscape.test.js index 4e27249fe0..09ef69d51d 100644 --- a/libraries/botbuilder-azure/tests/cosmosDbKeyEscape.test.js +++ b/libraries/botbuilder-azure/tests/cosmosDbKeyEscape.test.js @@ -6,7 +6,7 @@ describe('CosmosDbKeyEscape', function () { assert.throws( () => new CosmosDbKeyEscape(), Error, - 'constructor should have thrown error about missing key parameter.' + 'constructor should have thrown error about missing key parameter.', ); }); @@ -14,7 +14,7 @@ describe('CosmosDbKeyEscape', function () { assert.throws( () => new CosmosDbKeyEscape(''), Error, - 'constructor should have thrown error about missing key parameter.' + 'constructor should have thrown error about missing key parameter.', ); }); @@ -22,7 +22,7 @@ describe('CosmosDbKeyEscape', function () { assert.throws( () => new CosmosDbKeyEscape(' '), Error, - 'constructor should have thrown error about missing key parameter.' + 'constructor should have thrown error about missing key parameter.', ); }); diff --git a/libraries/botbuilder-azure/tests/cosmosDbPartitionedStorage.test.js b/libraries/botbuilder-azure/tests/cosmosDbPartitionedStorage.test.js index 92413e9e34..ed2bcf5743 100644 --- a/libraries/botbuilder-azure/tests/cosmosDbPartitionedStorage.test.js +++ b/libraries/botbuilder-azure/tests/cosmosDbPartitionedStorage.test.js @@ -63,7 +63,7 @@ const checkEmulator = async () => { } if (!canConnectToEmulator) console.warn( - `Unable to connect to Cosmos Emulator at ${emulatorEndpoint}. Running tests against Nock recordings.` + `Unable to connect to Cosmos Emulator at ${emulatorEndpoint}. Running tests against Nock recordings.`, ); } return canConnectToEmulator; @@ -130,339 +130,345 @@ const options = { scope: getSettings().cosmosDbEndpoint, }; -describe('CosmosDbPartitionedStorage - Constructor Tests', function () { - it('throws when provided with null options', function () { - assert.throws( - () => new CosmosDbPartitionedStorage(null), - ReferenceError('CosmosDbPartitionedStorageOptions is required.') - ); - }); +describe('CosmosDbPartitionedStorage', function () { + describe('Constructor Tests', function () { + it('throws when provided with null options', function () { + assert.throws( + () => new CosmosDbPartitionedStorage(null), + ReferenceError('CosmosDbPartitionedStorageOptions is required.'), + ); + }); - it('throws when no endpoint provided', function () { - const noEndpoint = getSettings(this.test); - noEndpoint.cosmosDbEndpoint = null; - assert.throws( - () => new CosmosDbPartitionedStorage(noEndpoint), - ReferenceError('cosmosDbEndpoint for CosmosDB is required.') - ); - }); + it('throws when no endpoint provided', function () { + const noEndpoint = getSettings(this.test); + noEndpoint.cosmosDbEndpoint = null; + assert.throws( + () => new CosmosDbPartitionedStorage(noEndpoint), + ReferenceError('cosmosDbEndpoint for CosmosDB is required.'), + ); + }); - it('throws when no authKey or tokenCredential provided', function () { - const noAuthKey = getSettings(this.test); - noAuthKey.authKey = null; - assert.throws( - () => new CosmosDbPartitionedStorage(noAuthKey), - ReferenceError('authKey or tokenCredential for CosmosDB is required.') - ); - }); + it('throws when no authKey or tokenCredential provided', function () { + const noAuthKey = getSettings(this.test); + noAuthKey.authKey = null; + assert.throws( + () => new CosmosDbPartitionedStorage(noAuthKey), + ReferenceError('authKey or tokenCredential for CosmosDB is required.'), + ); + }); - it('throws when no databaseId provided', function () { - const noDatabaseId = getSettings(this.test); - noDatabaseId.databaseId = null; - assert.throws( - () => new CosmosDbPartitionedStorage(noDatabaseId), - ReferenceError('databaseId is for CosmosDB required.') - ); - }); + it('throws when no databaseId provided', function () { + const noDatabaseId = getSettings(this.test); + noDatabaseId.databaseId = null; + assert.throws( + () => new CosmosDbPartitionedStorage(noDatabaseId), + ReferenceError('databaseId is for CosmosDB required.'), + ); + }); - it('throws when no containerId provided', function () { - const noContainerId = getSettings(this.test); - noContainerId.containerId = null; - assert.throws( - () => new CosmosDbPartitionedStorage(noContainerId), - ReferenceError('containerId for CosmosDB is required.') - ); + it('throws when no containerId provided', function () { + const noContainerId = getSettings(this.test); + noContainerId.containerId = null; + assert.throws( + () => new CosmosDbPartitionedStorage(noContainerId), + ReferenceError('containerId for CosmosDB is required.'), + ); + }); }); -}); -describe('CosmosDbPartitionedStorage - Base Storage Tests', function () { - this.timeout(5000); - before('cleanup', cleanup); // Ensure we start from scratch - beforeEach('prep', prep); - afterEach('cleanup', cleanup); + describe('Base Storage Tests', function () { + this.timeout(5000); - it('passes cosmosClientOptions to CosmosClient', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + before('cleanup', cleanup); + // Ensure we start from scratch + beforeEach('prep', prep); - const settingsWithClientOptions = getSettings(this.test); - settingsWithClientOptions.cosmosClientOptions = { - agent: new https.Agent({ rejectUnauthorized: false }), - connectionPolicy: { requestTimeout: 999 }, - userAgentSuffix: 'test', - }; + afterEach('cleanup', cleanup); - const client = new CosmosDbPartitionedStorage(settingsWithClientOptions); - await client.initialize(); // Force client to go through initialization + it('passes cosmosClientOptions to CosmosClient', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - assert.strictEqual(client.client.clientContext.connectionPolicy.requestTimeout, 999); - assert.strictEqual(client.client.clientContext.cosmosClientOptions.userAgentSuffix, 'test'); + const settingsWithClientOptions = getSettings(this.test); + settingsWithClientOptions.cosmosClientOptions = { + agent: new https.Agent({ rejectUnauthorized: false }), + connectionPolicy: { requestTimeout: 999 }, + userAgentSuffix: 'test', + }; - nockDone(); - }); + const client = new CosmosDbPartitionedStorage(settingsWithClientOptions); + await client.initialize(); // Force client to go through initialization - it('passes cosmosClientOptions with default userAgentSuffix', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + assert.strictEqual(client.client.clientContext.connectionPolicy.requestTimeout, 999); + assert.strictEqual(client.client.clientContext.cosmosClientOptions.userAgentSuffix, 'test'); - const settingsWithClientOptions = getSettings(this.test); - settingsWithClientOptions.cosmosClientOptions = { - agent: new https.Agent({ rejectUnauthorized: false }), - connectionPolicy: { requestTimeout: 999 }, - }; + nockDone(); + }); - const client = new CosmosDbPartitionedStorage(settingsWithClientOptions); - await client.initialize(); // Force client to go through initialization + it('passes cosmosClientOptions with default userAgentSuffix', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - const userAgent = client.container.clientContext.cosmosClientOptions.defaultHeaders['User-Agent'].split(' '); - const length = userAgent.length; + const settingsWithClientOptions = getSettings(this.test); + settingsWithClientOptions.cosmosClientOptions = { + agent: new https.Agent({ rejectUnauthorized: false }), + connectionPolicy: { requestTimeout: 999 }, + }; - assert.strictEqual(client.client.clientContext.connectionPolicy.requestTimeout, 999); - assert.strictEqual(userAgent[length - 1], pjson.version); - assert.strictEqual(userAgent[length - 2], pjson.name); + const client = new CosmosDbPartitionedStorage(settingsWithClientOptions); + await client.initialize(); // Force client to go through initialization - nockDone(); - }); + const userAgent = + client.container.clientContext.cosmosClientOptions.defaultHeaders['User-Agent'].split(' '); + const length = userAgent.length; - it('return empty object when reading unknown key', async function () { - const { nockDone } = await usingNock(this.test, mode, options); - const testRan = await StorageBaseTests.returnEmptyObjectWhenReadingUnknownKey(storage); + assert.strictEqual(client.client.clientContext.connectionPolicy.requestTimeout, 999); + assert.strictEqual(userAgent[length - 1], pjson.version); + assert.strictEqual(userAgent[length - 2], pjson.name); - assert.strictEqual(testRan, true); + nockDone(); + }); - nockDone(); - }); + it('return empty object when reading unknown key', async function () { + const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.returnEmptyObjectWhenReadingUnknownKey(storage); - it('throws when reading null keys', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + assert.strictEqual(testRan, true); - const testRan = await StorageBaseTests.handleNullKeysWhenReading(storage); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('throws when reading null keys', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('throws when writing null keys', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.handleNullKeysWhenReading(storage); - const testRan = await StorageBaseTests.handleNullKeysWhenWriting(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('throws when writing null keys', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('does not throw when writing no items', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.handleNullKeysWhenWriting(storage); - const testRan = await StorageBaseTests.doesNotThrowWhenWritingNoItems(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('does not throw when writing no items', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('create an object', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.doesNotThrowWhenWritingNoItems(storage); - const testRan = await StorageBaseTests.createObject(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('create an object', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('handle crazy keys', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.createObject(storage); - const testRan = await StorageBaseTests.handleCrazyKeys(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('handle crazy keys', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('update an object', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.handleCrazyKeys(storage); - const testRan = await StorageBaseTests.updateObject(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('update an object', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('delete an object', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.updateObject(storage); - const testRan = await StorageBaseTests.deleteObject(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('delete an object', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('does not throw when deleting an unknown object', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.deleteObject(storage); - const testRan = await StorageBaseTests.deleteUnknownObject(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('does not throw when deleting an unknown object', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('performs batch operations', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.deleteUnknownObject(storage); - const testRan = await StorageBaseTests.performBatchOperations(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('performs batch operations', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('proceeds through a waterfall dialog', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.performBatchOperations(storage); - const testRan = await StorageBaseTests.proceedsThroughWaterfall(storage); + assert.strictEqual(testRan, true); + nockDone(); + }); - assert.strictEqual(testRan, true); - nockDone(); - }); + it('proceeds through a waterfall dialog', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - it('support using multiple databases', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + const testRan = await StorageBaseTests.proceedsThroughWaterfall(storage); - const newDb = 'new-db'; + assert.strictEqual(testRan, true); + nockDone(); + }); - const defaultSettings = getSettings(this.test); - const settingsWithNewDb = getSettings(this.test); - settingsWithNewDb.databaseId = newDb; + it('support using multiple databases', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - // cosmosDbPartitionedStorage requires the user creates the db, - // so we need to create it for the test - const dbCreateClient = new CosmosClient({ - endpoint: settingsWithNewDb.cosmosDbEndpoint, - key: settingsWithNewDb.authKey, - agent: new https.Agent({ rejectUnauthorized: false }), - }); - try { - await dbCreateClient.database(newDb).delete(); - } catch { - //This throws if the db is already deleted. - } - await dbCreateClient.databases.create({ id: newDb }); + const newDb = 'new-db'; - const defaultClient = new CosmosDbPartitionedStorage(defaultSettings); - await assert.doesNotReject(async () => await defaultClient.initialize()); + const defaultSettings = getSettings(this.test); + const settingsWithNewDb = getSettings(this.test); + settingsWithNewDb.databaseId = newDb; - const newClient = new CosmosDbPartitionedStorage(settingsWithNewDb); - await assert.doesNotReject(async () => await newClient.initialize()); + // cosmosDbPartitionedStorage requires the user creates the db, + // so we need to create it for the test + const dbCreateClient = new CosmosClient({ + endpoint: settingsWithNewDb.cosmosDbEndpoint, + key: settingsWithNewDb.authKey, + agent: new https.Agent({ rejectUnauthorized: false }), + }); + try { + await dbCreateClient.database(newDb).delete(); + } catch { + //This throws if the db is already deleted. + } + await dbCreateClient.databases.create({ id: newDb }); - await assert.doesNotReject( - async () => await newClient.client.database(newDb).container(settingsWithNewDb.containerId).read() - ); + const defaultClient = new CosmosDbPartitionedStorage(defaultSettings); + await assert.doesNotReject(async () => await defaultClient.initialize()); - await dbCreateClient.database(newDb).delete(); + const newClient = new CosmosDbPartitionedStorage(settingsWithNewDb); + await assert.doesNotReject(async () => await newClient.initialize()); - nockDone(); - }); + await assert.doesNotReject( + async () => await newClient.client.database(newDb).container(settingsWithNewDb.containerId).read(), + ); - it('support using multiple containers', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + await dbCreateClient.database(newDb).delete(); - const newContainer = 'new-container'; + nockDone(); + }); - const defaultSettings = getSettings(this.test); - const settingsWithNewContainer = getSettings(this.test); - settingsWithNewContainer.containerId = newContainer; + it('support using multiple containers', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - const defaultClient = new CosmosDbPartitionedStorage(defaultSettings); - await assert.doesNotReject(async () => await defaultClient.initialize()); + const newContainer = 'new-container'; - const newClient = new CosmosDbPartitionedStorage(settingsWithNewContainer); - await assert.doesNotReject(async () => await newClient.initialize()); + const defaultSettings = getSettings(this.test); + const settingsWithNewContainer = getSettings(this.test); + settingsWithNewContainer.containerId = newContainer; - await assert.doesNotReject( - async () => - await newClient.client.database(settingsWithNewContainer.databaseId).container(newContainer).read() - ); + const defaultClient = new CosmosDbPartitionedStorage(defaultSettings); + await assert.doesNotReject(async () => await defaultClient.initialize()); - nockDone(); - }); + const newClient = new CosmosDbPartitionedStorage(settingsWithNewContainer); + await assert.doesNotReject(async () => await newClient.initialize()); - it('is aware of nesting limit', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + await assert.doesNotReject( + async () => + await newClient.client.database(settingsWithNewContainer.databaseId).container(newContainer).read(), + ); - async function testNest(depth) { - // This creates nested data with both objects and arrays - const createNestedData = (count, isArray = false) => - count > 0 - ? isArray - ? [createNestedData(count - 1, false)] - : { data: createNestedData(count - 1, true) } - : null; + nockDone(); + }); - const changes = { CONTEXTKEY: createNestedData(depth) }; + it('is aware of nesting limit', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - await storage.write(changes); - } + async function testNest(depth) { + // This creates nested data with both objects and arrays + const createNestedData = (count, isArray = false) => + count > 0 + ? isArray + ? [createNestedData(count - 1, false)] + : { data: createNestedData(count - 1, true) } + : null; - // Should not throw - await testNest(127); + const changes = { CONTEXTKEY: createNestedData(depth) }; - try { - // Should either not throw or throw a special exception - await testNest(128); - } catch (err) { - // If the nesting limit is changed on the Cosmos side - // then this assertion won't be reached, which is okay - assert.strictEqual(err.message.includes('recursion'), true); - } + await storage.write(changes); + } - nockDone(); - }); + // Should not throw + await testNest(127); - it('is aware of nesting limit with dialogs', async function () { - const { nockDone } = await usingNock(this.test, mode, options); + try { + // Should either not throw or throw a special exception + await testNest(128); + } catch (err) { + // If the nesting limit is changed on the Cosmos side + // then this assertion won't be reached, which is okay + assert.strictEqual(err.message.includes('recursion'), true); + } - async function testDialogNest(dialogDepth) { - const createNestedDialog = (depth) => - new ComponentDialog('componentDialog').addDialog( - depth > 0 - ? createNestedDialog(depth - 1) - : new WaterfallDialog('waterfallDialog', [async () => Dialog.EndOfTurn]) - ); + nockDone(); + }); - const convoState = new ConversationState(storage); - const dialogState = convoState.createProperty('dialogStateForNestingTest'); - const dialogs = new DialogSet(dialogState); + it('is aware of nesting limit with dialogs', async function () { + const { nockDone } = await usingNock(this.test, mode, options); - const adapter = new TestAdapter(async (turnContext) => { - if (turnContext.activity.text == 'reset') { - await dialogState.delete(turnContext); - } else { - const dc = await dialogs.createContext(turnContext); + async function testDialogNest(dialogDepth) { + const createNestedDialog = (depth) => + new ComponentDialog('componentDialog').addDialog( + depth > 0 + ? createNestedDialog(depth - 1) + : new WaterfallDialog('waterfallDialog', [async () => Dialog.EndOfTurn]), + ); - await dc.beginDialog('componentDialog'); - } - }).use(new AutoSaveStateMiddleware(convoState)); + const convoState = new ConversationState(storage); + const dialogState = convoState.createProperty('dialogStateForNestingTest'); + const dialogs = new DialogSet(dialogState); - adapter.conversation = TestAdapter.createConversation('nestingTest'); + const adapter = new TestAdapter(async (turnContext) => { + if (turnContext.activity.text == 'reset') { + await dialogState.delete(turnContext); + } else { + const dc = await dialogs.createContext(turnContext); - dialogs.add(createNestedDialog(dialogDepth)); + await dc.beginDialog('componentDialog'); + } + }).use(new AutoSaveStateMiddleware(convoState)); - await adapter.send('reset').send('hello').startTest(); - } + adapter.conversation = TestAdapter.createConversation('nestingTest'); - // Note that the C# test places the "cutoff" between 23 and 24 dialogs. - // These tests use 29 and 30 because Node doesn't nest data as deeply - // due to the lack of type names, so more dialogs are needed to reach - // the limit of 128 levels. + dialogs.add(createNestedDialog(dialogDepth)); - // Should not throw - await testDialogNest(29); + await adapter.send('reset').send('hello').startTest(); + } - try { - // Should either not throw or throw a special exception - await testDialogNest(30); - } catch (err) { - // If the nesting limit is changed on the Cosmos side - // then this assertion won't be reached, which is okay - assert.strictEqual(err.message.includes('dialogs'), true); - } + // Note that the C# test places the "cutoff" between 23 and 24 dialogs. + // These tests use 29 and 30 because Node doesn't nest data as deeply + // due to the lack of type names, so more dialogs are needed to reach + // the limit of 128 levels. + + // Should not throw + await testDialogNest(29); - nockDone(); + try { + // Should either not throw or throw a special exception + await testDialogNest(30); + } catch (err) { + // If the nesting limit is changed on the Cosmos side + // then this assertion won't be reached, which is okay + assert.strictEqual(err.message.includes('dialogs'), true); + } + + nockDone(); + }); }); });