diff --git a/packages/compass-e2e-tests/helpers/insert-data.ts b/packages/compass-e2e-tests/helpers/mongo-clients.ts similarity index 96% rename from packages/compass-e2e-tests/helpers/insert-data.ts rename to packages/compass-e2e-tests/helpers/mongo-clients.ts index 0f2019e0788..e6d700badda 100644 --- a/packages/compass-e2e-tests/helpers/insert-data.ts +++ b/packages/compass-e2e-tests/helpers/mongo-clients.ts @@ -2,6 +2,7 @@ import { MongoClient } from 'mongodb'; import type { Db, MongoServerError } from 'mongodb'; import { getDefaultConnectionStrings } from './test-runner-context'; import { redactConnectionString } from 'mongodb-connection-string-url'; +import { noServerWarningsCheckpoint } from './test-runner-global-fixtures'; // This is a list of all the known database names that get created by tests so // that we can know what to drop when we clean up before every test. If a new @@ -86,9 +87,15 @@ export const beforeEach = async () => { await Promise.all(promises); }; +export const afterEach = () => { + // Check for unexpected server warnings after each test + noServerWarningsCheckpoint(); +}; + export const mochaRootHooks: Mocha.RootHookObject = { beforeAll, beforeEach, + afterEach, afterAll, }; diff --git a/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts b/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts index f151643c232..af8d48433b4 100644 --- a/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts +++ b/packages/compass-e2e-tests/helpers/test-runner-global-fixtures.ts @@ -14,7 +14,12 @@ import { } from './test-runner-context'; import { E2E_WORKSPACE_PATH, LOG_PATH } from './test-runner-paths'; import Debug from 'debug'; -import { startTestServer } from '@mongodb-js/compass-test-server'; +import { + startTestServer, + ServerLogsChecker, + type LogEntry, + type WarningFilter, +} from '@mongodb-js/compass-test-server'; import { MongoClient } from 'mongodb'; import { isEnterprise } from 'mongodb-build-info'; import { @@ -23,6 +28,7 @@ import { rebuildNativeModules, removeUserDataDir, screenshotPathName, + serverSatisfies, startBrowser, } from './compass'; import { getConnectionTitle } from '@mongodb-js/connection-info'; @@ -51,6 +57,28 @@ const debug = Debug('compass-e2e-tests:mocha-global-fixtures'); const cleanupFns: (() => Promise | void)[] = []; +export const serverLogsCheckers: ServerLogsChecker[] = []; + +export function noServerWarningsCheckpoint() { + for (const checker of serverLogsCheckers) { + checker.noServerWarningsCheckpoint(); + } +} + +export function allowServerWarnings(...filters: WarningFilter[]): () => void { + const unsubscribeFns: (() => void)[] = []; + for (const filter of filters) { + for (const checker of serverLogsCheckers) { + unsubscribeFns.push(checker.allowWarning(filter)); + } + } + return () => { + for (const fn of unsubscribeFns) { + fn(); + } + }; +} + async function createAtlasCloudResources() { assertTestingAtlasCloud(context); @@ -264,6 +292,7 @@ export async function mochaGlobalSetup(this: Mocha.Runner) { getConnectionTitle(connectionInfo) ); const server = await startTestServer(connectionInfo.testServer); + serverLogsCheckers.push(new ServerLogsChecker(server)); cleanupFns.push(() => { debug( 'Stopping server for connection %s', @@ -314,6 +343,17 @@ export async function mochaGlobalSetup(this: Mocha.Runner) { debug('Getting mongodb server info'); await updateMongoDBServerInfo(); + if (serverSatisfies('< 8.2')) { + for (const checker of serverLogsCheckers) { + checker.allowWarning((l: LogEntry) => { + // "Aggregate command executor error" with CommandNotSupported + // This happens when Compass probes for search index support on older non-Atlas servers + return ( + l.id === 23799 && l.attr?.error?.codeName === 'CommandNotSupported' + ); + }); + } + } throwIfAborted(); @@ -364,6 +404,13 @@ export async function mochaGlobalSetup(this: Mocha.Runner) { export async function mochaGlobalTeardown() { debug('Cleaning up after the tests ...'); + + // Close server log checkers + for (const checker of serverLogsCheckers) { + checker.close(); + } + serverLogsCheckers.length = 0; + await Promise.allSettled( cleanupFns.map((fn) => { // We get a mix of sync and non-sync functions here. Awaiting even the diff --git a/packages/compass-e2e-tests/index.ts b/packages/compass-e2e-tests/index.ts index 9a296fb574d..12478453881 100644 --- a/packages/compass-e2e-tests/index.ts +++ b/packages/compass-e2e-tests/index.ts @@ -10,7 +10,7 @@ import { mochaGlobalSetup, mochaGlobalTeardown, } from './helpers/test-runner-global-fixtures'; -import { mochaRootHooks } from './helpers/insert-data'; +import { mochaRootHooks } from './helpers/mongo-clients'; // @ts-expect-error no types for this package import logRunning from 'why-is-node-running'; diff --git a/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts b/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts index 1873eb83503..711a5a1b3b0 100644 --- a/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts +++ b/packages/compass-e2e-tests/tests/atlas-cloud/collection-ai-query.test.ts @@ -9,7 +9,7 @@ import { } from '../../helpers/compass'; import type { Compass } from '../../helpers/compass'; import * as Selectors from '../../helpers/selectors'; -import { createNumbersCollection } from '../../helpers/insert-data'; +import { createNumbersCollection } from '../../helpers/mongo-clients'; import { isTestingAtlasCloud } from '../../helpers/test-runner-context'; import { switchPipelineMode } from '../../helpers/commands/switch-pipeline-mode'; diff --git a/packages/compass-e2e-tests/tests/atlas-cloud/global-writes.test.ts b/packages/compass-e2e-tests/tests/atlas-cloud/global-writes.test.ts index 248b482fac3..169ced031df 100644 --- a/packages/compass-e2e-tests/tests/atlas-cloud/global-writes.test.ts +++ b/packages/compass-e2e-tests/tests/atlas-cloud/global-writes.test.ts @@ -7,7 +7,7 @@ import { Selectors, } from '../../helpers/compass'; import type { CompassBrowser } from '../../helpers/compass-browser'; -import { createGeospatialCollection } from '../../helpers/insert-data'; +import { createGeospatialCollection } from '../../helpers/mongo-clients'; import { getDefaultConnectionNames, isTestingAtlasCloud, diff --git a/packages/compass-e2e-tests/tests/atlas-cloud/rolling-indexes.test.ts b/packages/compass-e2e-tests/tests/atlas-cloud/rolling-indexes.test.ts index 72f0897b2a4..a5c83bb32de 100644 --- a/packages/compass-e2e-tests/tests/atlas-cloud/rolling-indexes.test.ts +++ b/packages/compass-e2e-tests/tests/atlas-cloud/rolling-indexes.test.ts @@ -6,7 +6,7 @@ import { Selectors, } from '../../helpers/compass'; import type { CompassBrowser } from '../../helpers/compass-browser'; -import { createNumbersCollection } from '../../helpers/insert-data'; +import { createNumbersCollection } from '../../helpers/mongo-clients'; import { getDefaultConnectionNames, isTestingAtlasCloud, diff --git a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts index ecc234474d2..9cbd84560f5 100644 --- a/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-aggregations-tab.test.ts @@ -15,11 +15,13 @@ import * as Selectors from '../helpers/selectors'; import { createNestedDocumentsCollection, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; import { saveAggregationPipeline } from '../helpers/commands/save-aggregation-pipeline'; import type { ChainablePromiseElement } from 'webdriverio'; import { switchPipelineMode } from '../helpers/commands/switch-pipeline-mode'; import { isTestingWeb } from '../helpers/test-runner-context'; +import { allowServerWarnings } from '../helpers/test-runner-global-fixtures'; +import type { LogEntry } from '@mongodb-js/compass-test-server'; const { expect } = chai; @@ -472,6 +474,26 @@ describe('Collection aggregations tab', function () { describe('maxTimeMS', function () { let maxTimeMSBefore: any; + let unsubscribeAllowWarnings: () => void; + + before(function () { + unsubscribeAllowWarnings = allowServerWarnings( + 8996503, // Allow "$function is deprecated" warning + (l: LogEntry) => { + // 23798 = "Plan executor error", 23799 = "Aggregate command executor error" + return ( + l.id === 23799 && + ['MaxTimeMSExpired', 'Interrupted'].includes( + l.attr?.error?.codeName + ) + ); + } + ); + }); + + after(function () { + unsubscribeAllowWarnings(); + }); beforeEach(async function () { maxTimeMSBefore = await browser.getFeature('maxTimeMS'); @@ -653,6 +675,21 @@ describe('Collection aggregations tab', function () { }); }); + let unsubscribeAllowWarnings: () => void; + + before(function () { + unsubscribeAllowWarnings = allowServerWarnings((l: LogEntry) => { + return ( + l.id === 23799 && + ['DocumentValidationFailure'].includes(l.attr?.error?.codeName) + ); + }); + }); + + after(function () { + unsubscribeAllowWarnings(); + }); + it('Shows error info when inserting', async function () { await browser.selectStageOperator(0, '$out'); await browser.setCodemirrorEditorValue( @@ -951,31 +988,46 @@ describe('Collection aggregations tab', function () { }); it('supports cancelling long-running aggregations', async function () { - const slowQuery = `{ - sleep: { - $function: { - body: function () { - return sleep(10000) || true; + const unsubscribeAllowWarnings = allowServerWarnings( + 8996503, // Allow "$function is deprecated" warning + (l: LogEntry) => { + return ( + l.id === 23799 && ['Interrupted'].includes(l.attr?.error?.codeName) + ); + } + ); + try { + const slowQuery = `{ + sleep: { + $function: { + body: function () { + return sleep(10000) || true; + }, + args: [], + lang: "js", }, - args: [], - lang: "js", }, - }, - }`; + }`; - // Set first stage to a very slow $addFields - await browser.selectStageOperator(0, '$addFields'); - await browser.setCodemirrorEditorValue(Selectors.stageEditor(0), slowQuery); - - // Run and wait for results - await goToRunAggregation(browser); + // Set first stage to a very slow $addFields + await browser.selectStageOperator(0, '$addFields'); + await browser.setCodemirrorEditorValue( + Selectors.stageEditor(0), + slowQuery + ); - // Cancel aggregation run - await browser.clickVisible(Selectors.AggregationResultsCancelButton); - // Wait for the empty results banner (this is our indicator that we didn't - // load anything and dismissed "Loading" banner) - const emptyResultsBanner = browser.$(Selectors.AggregationEmptyResults); - await emptyResultsBanner.waitForDisplayed(); + // Run and wait for results + await goToRunAggregation(browser); + + // Cancel aggregation run + await browser.clickVisible(Selectors.AggregationResultsCancelButton); + // Wait for the empty results banner (this is our indicator that we didn't + // load anything and dismissed "Loading" banner) + const emptyResultsBanner = browser.$(Selectors.AggregationEmptyResults); + await emptyResultsBanner.waitForDisplayed(); + } finally { + unsubscribeAllowWarnings(); + } }); it('handles errors in aggregations', async function () { diff --git a/packages/compass-e2e-tests/tests/collection-ai-query.test.ts b/packages/compass-e2e-tests/tests/collection-ai-query.test.ts index 95f7d96beb8..6254262bcbe 100644 --- a/packages/compass-e2e-tests/tests/collection-ai-query.test.ts +++ b/packages/compass-e2e-tests/tests/collection-ai-query.test.ts @@ -10,7 +10,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import { startMockAssistantServer } from '../helpers/assistant-service'; async function setup( diff --git a/packages/compass-e2e-tests/tests/collection-bulk-delete.test.ts b/packages/compass-e2e-tests/tests/collection-bulk-delete.test.ts index a29dc4756bb..98de862b30c 100644 --- a/packages/compass-e2e-tests/tests/collection-bulk-delete.test.ts +++ b/packages/compass-e2e-tests/tests/collection-bulk-delete.test.ts @@ -13,7 +13,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import { context } from '../helpers/test-runner-context'; describe('Bulk Delete', function () { diff --git a/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts b/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts index 0bc903ffd1b..947de443c8c 100644 --- a/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts +++ b/packages/compass-e2e-tests/tests/collection-bulk-update.test.ts @@ -14,7 +14,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; describe('Bulk Update', () => { let compass: Compass; diff --git a/packages/compass-e2e-tests/tests/collection-documents-tab.test.ts b/packages/compass-e2e-tests/tests/collection-documents-tab.test.ts index 527a818ee90..2d16da60286 100644 --- a/packages/compass-e2e-tests/tests/collection-documents-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-documents-tab.test.ts @@ -17,13 +17,15 @@ import * as Selectors from '../helpers/selectors'; import { createNestedDocumentsCollection, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; import { isTestingWeb, context as testRunnerContext, } from '../helpers/test-runner-context'; import type { ChainablePromiseElement } from 'webdriverio'; import { tryToInsertDocument } from '../helpers/commands/try-to-insert-document'; +import { allowServerWarnings } from '../helpers/test-runner-global-fixtures'; +import type { LogEntry } from '@mongodb-js/compass-test-server'; const { expect } = chai; @@ -237,91 +239,113 @@ describe('Collection documents tab', function () { } }); - it('supports cancelling a find and then running another query', async function () { - // execute a query that will take a long time - await browser.runFindOperation( - 'Documents', - '{ $where: function() { return sleep(10000) || true; } }', - { - waitForResult: false, - } - ); - - // stop it - const documentListFetchingElement = browser.$( - Selectors.DocumentListFetching - ); - await documentListFetchingElement.waitForDisplayed(); - - await browser.clickVisible(Selectors.DocumentListFetchingStopButton); - - const documentListErrorElement = browser.$(Selectors.DocumentListError); - await documentListErrorElement.waitForDisplayed(); - - const errorText = await documentListErrorElement.getText(); - expect(errorText).to.equal('This operation was aborted'); - - // execute another (small, fast) query - await browser.runFindOperation('Documents', '{ i: 5 }'); - const documentListActionBarMessageElement = browser.$( - Selectors.DocumentListActionBarMessage - ); - - const displayText = await documentListActionBarMessageElement.getText(); - expect(displayText).to.equal('1 – 1 of 1'); - - if (!isTestingWeb()) { - // no query history in compass-web yet - const queries = await getRecentQueries(browser, true); - expect(queries).to.deep.include.members([ - { - Filter: - "{\n $where: 'function() { return sleep(10000) || true; }'\n}", - }, - ]); - } - }); - - for (const maxTimeMSMode of ['ui', 'preference'] as const) { - it(`supports maxTimeMS (set via ${maxTimeMSMode})`, async function () { - if (maxTimeMSMode === 'preference') { - if (isTestingWeb()) { - await browser.setFeature('maxTimeMS', 1); - } else { - await browser.openSettingsModal(); - await browser.waitForOpenModal(Selectors.SettingsModal); - await browser.clickVisible(Selectors.GeneralSettingsButton); - - await browser.setValueVisible( - Selectors.SettingsInputElement('maxTimeMS'), - '1' + context('cancel and maxTimeMS', function () { + let unsubscribeAllowWarningsFilter: () => void; + + before(function () { + unsubscribeAllowWarningsFilter = allowServerWarnings( + 8996500, // allow "$where is deprecated" warnings + (l: LogEntry) => { + return ( + l.id === 23798 && + ['MaxTimeMSExpired', 'ClientDisconnect'].includes( + l.attr?.error?.codeName + ) ); - await browser.clickVisible(Selectors.SaveSettingsButton); - await browser.waitForOpenModal(Selectors.SettingsModal, { - reverse: true, - }); } - } + ); + }); - // execute a query that will take a long time, but set a maxTimeMS shorter than that + it('supports cancelling a find and then running another query', async function () { + // execute a query that will take a long time await browser.runFindOperation( 'Documents', '{ $where: function() { return sleep(10000) || true; } }', { - ...(maxTimeMSMode === 'ui' ? { maxTimeMS: '1' } : {}), waitForResult: false, } ); + // stop it + const documentListFetchingElement = browser.$( + Selectors.DocumentListFetching + ); + await documentListFetchingElement.waitForDisplayed(); + + await browser.clickVisible(Selectors.DocumentListFetchingStopButton); + const documentListErrorElement = browser.$(Selectors.DocumentListError); await documentListErrorElement.waitForDisplayed(); const errorText = await documentListErrorElement.getText(); - expect(errorText).to.include( - 'Operation exceeded time limit. Please try increasing the maxTimeMS for the query in the expanded filter options.' + expect(errorText).to.equal('This operation was aborted'); + + // execute another (small, fast) query + await browser.runFindOperation('Documents', '{ i: 5 }'); + const documentListActionBarMessageElement = browser.$( + Selectors.DocumentListActionBarMessage ); + + const displayText = await documentListActionBarMessageElement.getText(); + expect(displayText).to.equal('1 – 1 of 1'); + + if (!isTestingWeb()) { + // no query history in compass-web yet + const queries = await getRecentQueries(browser, true); + expect(queries).to.deep.include.members([ + { + Filter: + "{\n $where: 'function() { return sleep(10000) || true; }'\n}", + }, + ]); + } }); - } + + for (const maxTimeMSMode of ['ui', 'preference'] as const) { + it(`supports maxTimeMS (set via ${maxTimeMSMode})`, async function () { + if (maxTimeMSMode === 'preference') { + if (isTestingWeb()) { + await browser.setFeature('maxTimeMS', 1); + } else { + await browser.openSettingsModal(); + await browser.waitForOpenModal(Selectors.SettingsModal); + await browser.clickVisible(Selectors.GeneralSettingsButton); + + await browser.setValueVisible( + Selectors.SettingsInputElement('maxTimeMS'), + '1' + ); + await browser.clickVisible(Selectors.SaveSettingsButton); + await browser.waitForOpenModal(Selectors.SettingsModal, { + reverse: true, + }); + } + } + + // execute a query that will take a long time, but set a maxTimeMS shorter than that + await browser.runFindOperation( + 'Documents', + '{ $where: function() { return sleep(10000) || true; } }', + { + ...(maxTimeMSMode === 'ui' ? { maxTimeMS: '1' } : {}), + waitForResult: false, + } + ); + + const documentListErrorElement = browser.$(Selectors.DocumentListError); + await documentListErrorElement.waitForDisplayed(); + + const errorText = await documentListErrorElement.getText(); + expect(errorText).to.include( + 'Operation exceeded time limit. Please try increasing the maxTimeMS for the query in the expanded filter options.' + ); + }); + } + + after(function () { + unsubscribeAllowWarningsFilter(); + }); + }); it('keeps the query when navigating to schema', async function () { await browser.runFindOperation('Documents', '{ i: 5 }'); @@ -736,7 +760,22 @@ FindIterable result = collection.find(filter);`); }); }); - describe('Editing', function () { + describe('Error info when editing', function () { + let unsubscribeAllowWarningsFilter: () => void; + + before(function () { + unsubscribeAllowWarningsFilter = allowServerWarnings((l: LogEntry) => { + return ( + l.id === 7267501 && + ['DocumentValidationFailure'].includes(l.attr?.error?.codeName) + ); + }); + }); + + after(function () { + unsubscribeAllowWarningsFilter(); + }); + beforeEach(async function () { await browser.navigateWithinCurrentCollectionTabs('Documents'); await tryToInsertDocument(browser, `{ "phone": 12345 }`); diff --git a/packages/compass-e2e-tests/tests/collection-export.test.ts b/packages/compass-e2e-tests/tests/collection-export.test.ts index 1f9813c9ee5..af80d08f24f 100644 --- a/packages/compass-e2e-tests/tests/collection-export.test.ts +++ b/packages/compass-e2e-tests/tests/collection-export.test.ts @@ -20,7 +20,9 @@ import * as Selectors from '../helpers/selectors'; import { createNumbersCollection, createNumbersStringCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; +import { allowServerWarnings } from '../helpers/test-runner-global-fixtures'; +import type { LogEntry } from '@mongodb-js/compass-test-server'; async function selectExportFileTypeCSV(browser: CompassBrowser) { await browser.clickParent(Selectors.FileTypeCSV); @@ -582,273 +584,301 @@ describe('Collection export', function () { }); }); - it('can abort an in progress CSV export', async function () { - const telemetryEntry = await browser.listenForTelemetryEvents(telemetry); - - // Set a query that we'll use. - await browser.runFindOperation( - 'Documents', - '{ $where: "function() { sleep(100); return true; }" }' - ); - - // Open the modal. - await browser.clickVisible(Selectors.ExportCollectionMenuButton); - await browser.clickVisible(Selectors.ExportCollectionQueryOption); - await browser.waitForOpenModal(Selectors.ExportModal); - - // Choose to export select fields. - await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); - await browser.clickVisible(Selectors.ExportNextStepButton); - - // Click to export the `i` and `j` fields. - await toggleExportFieldCheckbox(browser, 'i'); - await toggleExportFieldCheckbox(browser, 'j'); - - await browser.clickVisible(Selectors.ExportNextStepButton); - - // Select CSV. - await selectExportFileTypeCSV(browser); - - await browser.clickVisible(Selectors.ExportModalExportButton); - - const filename = outputFilename('aborted-export-test.csv'); - await browser.setExportFilename(filename); - - // Wait for the modal to go away. - await browser.waitForOpenModal(Selectors.ExportModal, { - reverse: true, + describe('aborting exports', function () { + let unsubscribeAllowWarningsFilter: () => void; + + before(function () { + unsubscribeAllowWarningsFilter = allowServerWarnings( + 8996500, // allow "$where is deprecated" warnings + (l: LogEntry) => { + return ( + l.id === 23798 && + ['QueryPlanKilled', 'ClientDisconnect'].includes( + l.attr?.error?.codeName + ) + ); + } + ); }); - // Wait for the export to start and then click stop. - const exportAbortButton = browser.$(Selectors.ExportToastAbort); - await exportAbortButton.waitForDisplayed(); - await exportAbortButton.click(); - - // Wait for the aborted toast to appear. - const toastElement = browser.$(Selectors.ExportToast); - await toastElement.waitForDisplayed(); - await browser - .$(Selectors.closeToastButton(Selectors.ExportToast)) - .waitForDisplayed(); - - // Check it displays that the export was aborted. - const toastText = await toastElement.getText(); - try { - expect(toastText).to.include('Export aborted'); - } catch (err) { - console.log(toastText); - throw err; - } - - // Close the toast. - await browser - .$(Selectors.closeToastButton(Selectors.ExportToast)) - .waitForDisplayed(); - await browser.clickVisible( - Selectors.closeToastButton(Selectors.ExportToast) - ); - await toastElement.waitForDisplayed({ reverse: true }); - - // Confirm that we exported what we expected to export. - const text = await fs.readFile(filename, 'utf-8'); - const lines = text.split(/\r?\n/); - expect(lines.length).to.equal(1); - // We abort before we add the columns to the file. - expect(lines[0]).to.equal(''); - - const exportCompletedEvent = await telemetryEntry('Export Completed'); - delete exportCompletedEvent.duration; // Duration varies. - deleteCommonVariedProperties(exportCompletedEvent); - expect(exportCompletedEvent).to.deep.equal({ - all_docs: false, - file_type: 'csv', - field_count: 2, - field_option: 'select-fields', - number_of_docs: 0, - has_projection: false, - fields_added_count: 0, - fields_not_selected_count: 1, - success: true, - type: 'query', - stopped: true, + after(function () { + unsubscribeAllowWarningsFilter(); }); - expect(telemetry.screens()).to.include('export_modal'); - }); - - it('can abort an in progress JSON export', async function () { - const telemetryEntry = await browser.listenForTelemetryEvents(telemetry); - // Set a query that we'll use. - await browser.runFindOperation( - 'Documents', - '{ $where: "function() { sleep(100); return true; }" }' - ); - - // Open the modal. - await browser.clickVisible(Selectors.ExportCollectionMenuButton); - await browser.clickVisible(Selectors.ExportCollectionQueryOption); - await browser.waitForOpenModal(Selectors.ExportModal); - - // Choose to export select fields. - await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); - await browser.clickVisible(Selectors.ExportNextStepButton); - - // Click to export the `i` and `j` fields. - await toggleExportFieldCheckbox(browser, 'i'); - await toggleExportFieldCheckbox(browser, 'j'); - - await browser.clickVisible(Selectors.ExportNextStepButton); - - // File type defaults to JSON export. - await browser.clickVisible(Selectors.ExportModalExportButton); - - const filename = outputFilename('aborted-export-test.json'); - await browser.setExportFilename(filename); - - // Wait for the modal to go away. - await browser.waitForOpenModal(Selectors.ExportModal, { - reverse: true, + it('can abort an in progress CSV export', async function () { + const telemetryEntry = await browser.listenForTelemetryEvents( + telemetry + ); + + // Set a query that we'll use. + await browser.runFindOperation( + 'Documents', + '{ $where: "function() { sleep(100); return true; }" }' + ); + + // Open the modal. + await browser.clickVisible(Selectors.ExportCollectionMenuButton); + await browser.clickVisible(Selectors.ExportCollectionQueryOption); + await browser.waitForOpenModal(Selectors.ExportModal); + + // Choose to export select fields. + await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); + await browser.clickVisible(Selectors.ExportNextStepButton); + + // Click to export the `i` and `j` fields. + await toggleExportFieldCheckbox(browser, 'i'); + await toggleExportFieldCheckbox(browser, 'j'); + + await browser.clickVisible(Selectors.ExportNextStepButton); + + // Select CSV. + await selectExportFileTypeCSV(browser); + + await browser.clickVisible(Selectors.ExportModalExportButton); + + const filename = outputFilename('aborted-export-test.csv'); + await browser.setExportFilename(filename); + + // Wait for the modal to go away. + await browser.waitForOpenModal(Selectors.ExportModal, { + reverse: true, + }); + + // Wait for the export to start and then click stop. + const exportAbortButton = browser.$(Selectors.ExportToastAbort); + await exportAbortButton.waitForDisplayed(); + await exportAbortButton.click(); + + // Wait for the aborted toast to appear. + const toastElement = browser.$(Selectors.ExportToast); + await toastElement.waitForDisplayed(); + await browser + .$(Selectors.closeToastButton(Selectors.ExportToast)) + .waitForDisplayed(); + + // Check it displays that the export was aborted. + const toastText = await toastElement.getText(); + try { + expect(toastText).to.include('Export aborted'); + } catch (err) { + console.log(toastText); + throw err; + } + + // Close the toast. + await browser + .$(Selectors.closeToastButton(Selectors.ExportToast)) + .waitForDisplayed(); + await browser.clickVisible( + Selectors.closeToastButton(Selectors.ExportToast) + ); + await toastElement.waitForDisplayed({ reverse: true }); + + // Confirm that we exported what we expected to export. + const text = await fs.readFile(filename, 'utf-8'); + const lines = text.split(/\r?\n/); + expect(lines.length).to.equal(1); + // We abort before we add the columns to the file. + expect(lines[0]).to.equal(''); + + const exportCompletedEvent = await telemetryEntry('Export Completed'); + delete exportCompletedEvent.duration; // Duration varies. + deleteCommonVariedProperties(exportCompletedEvent); + expect(exportCompletedEvent).to.deep.equal({ + all_docs: false, + file_type: 'csv', + field_count: 2, + field_option: 'select-fields', + number_of_docs: 0, + has_projection: false, + fields_added_count: 0, + fields_not_selected_count: 1, + success: true, + type: 'query', + stopped: true, + }); + expect(telemetry.screens()).to.include('export_modal'); }); - // Wait for the export to start and then click stop. - const exportAbortButton = browser.$(Selectors.ExportToastAbort); - await exportAbortButton.waitForDisplayed(); - await exportAbortButton.click(); - - // Wait for the aborted toast to appear. - const toastElement = browser.$(Selectors.ExportToast); - await toastElement.waitForDisplayed(); - await browser - .$(Selectors.closeToastButton(Selectors.ExportToast)) - .waitForDisplayed(); - - // Check it displays that the export was aborted. - const toastText = await toastElement.getText(); - try { - expect(toastText).to.include('Export aborted'); - } catch (err) { - console.log(toastText); - throw err; - } - - // Close the toast. - await browser - .$(Selectors.closeToastButton(Selectors.ExportToast)) - .waitForDisplayed(); - await browser.clickVisible( - Selectors.closeToastButton(Selectors.ExportToast) - ); - await toastElement.waitForDisplayed({ reverse: true }); - - const exportCompletedEvent = await telemetryEntry('Export Completed'); - delete exportCompletedEvent.duration; // Duration varies. - deleteCommonVariedProperties(exportCompletedEvent); - expect(exportCompletedEvent).to.deep.equal({ - all_docs: false, - file_type: 'json', - json_format: 'default', - field_count: 2, - fields_added_count: 0, - fields_not_selected_count: 1, - field_option: 'select-fields', - number_of_docs: 0, - has_projection: false, - success: true, - type: 'query', - stopped: true, - }); - expect(telemetry.screens()).to.include('export_modal'); - }); - - it('aborts an in progress CSV export when disconnected', async function () { - const telemetryEntry = await browser.listenForTelemetryEvents(telemetry); - - // Set a query that we'll use. - await browser.runFindOperation( - 'Documents', - '{ $where: "function() { sleep(100); return true; }" }' - ); - - // Open the modal. - await browser.clickVisible(Selectors.ExportCollectionMenuButton); - await browser.clickVisible(Selectors.ExportCollectionQueryOption); - await browser.waitForOpenModal(Selectors.ExportModal); - - // Choose to export select fields. - await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); - await browser.clickVisible(Selectors.ExportNextStepButton); - - // Click to export the `i` and `j` fields. - await toggleExportFieldCheckbox(browser, 'i'); - await toggleExportFieldCheckbox(browser, 'j'); - - await browser.clickVisible(Selectors.ExportNextStepButton); - - // Select CSV. - await selectExportFileTypeCSV(browser); - - await browser.clickVisible(Selectors.ExportModalExportButton); - - const filename = outputFilename('disconnected-export-test.csv'); - await browser.setExportFilename(filename); - - // Wait for the modal to go away. - await browser.waitForOpenModal(Selectors.ExportModal, { - reverse: true, + it('can abort an in progress JSON export', async function () { + const telemetryEntry = await browser.listenForTelemetryEvents( + telemetry + ); + + // Set a query that we'll use. + await browser.runFindOperation( + 'Documents', + '{ $where: "function() { sleep(100); return true; }" }' + ); + + // Open the modal. + await browser.clickVisible(Selectors.ExportCollectionMenuButton); + await browser.clickVisible(Selectors.ExportCollectionQueryOption); + await browser.waitForOpenModal(Selectors.ExportModal); + + // Choose to export select fields. + await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); + await browser.clickVisible(Selectors.ExportNextStepButton); + + // Click to export the `i` and `j` fields. + await toggleExportFieldCheckbox(browser, 'i'); + await toggleExportFieldCheckbox(browser, 'j'); + + await browser.clickVisible(Selectors.ExportNextStepButton); + + // File type defaults to JSON export. + await browser.clickVisible(Selectors.ExportModalExportButton); + + const filename = outputFilename('aborted-export-test.json'); + await browser.setExportFilename(filename); + + // Wait for the modal to go away. + await browser.waitForOpenModal(Selectors.ExportModal, { + reverse: true, + }); + + // Wait for the export to start and then click stop. + const exportAbortButton = browser.$(Selectors.ExportToastAbort); + await exportAbortButton.waitForDisplayed(); + await exportAbortButton.click(); + + // Wait for the aborted toast to appear. + const toastElement = browser.$(Selectors.ExportToast); + await toastElement.waitForDisplayed(); + await browser + .$(Selectors.closeToastButton(Selectors.ExportToast)) + .waitForDisplayed(); + + // Check it displays that the export was aborted. + const toastText = await toastElement.getText(); + try { + expect(toastText).to.include('Export aborted'); + } catch (err) { + console.log(toastText); + throw err; + } + + // Close the toast. + await browser + .$(Selectors.closeToastButton(Selectors.ExportToast)) + .waitForDisplayed(); + await browser.clickVisible( + Selectors.closeToastButton(Selectors.ExportToast) + ); + await toastElement.waitForDisplayed({ reverse: true }); + + const exportCompletedEvent = await telemetryEntry('Export Completed'); + delete exportCompletedEvent.duration; // Duration varies. + deleteCommonVariedProperties(exportCompletedEvent); + expect(exportCompletedEvent).to.deep.equal({ + all_docs: false, + file_type: 'json', + json_format: 'default', + field_count: 2, + fields_added_count: 0, + fields_not_selected_count: 1, + field_option: 'select-fields', + number_of_docs: 0, + has_projection: false, + success: true, + type: 'query', + stopped: true, + }); + expect(telemetry.screens()).to.include('export_modal'); }); - // Wait for the export to start. - const exportAbortButton = browser.$(Selectors.ExportToastAbort); - await exportAbortButton.waitForDisplayed(); - - await browser.disconnectAll({ closeToasts: false }); - - // Wait for the aborted toast to appear. - const toastElement = browser.$(Selectors.ExportToast); - await toastElement.waitForDisplayed(); - await browser - .$(Selectors.closeToastButton(Selectors.ExportToast)) - .waitForDisplayed(); - - // Check it displays that the export was aborted. - const toastText = await toastElement.getText(); - try { - expect(toastText).to.include('Export aborted'); - } catch (err) { - console.log(toastText); - throw err; - } - - // Close the toast. - await browser.clickVisible( - Selectors.closeToastButton(Selectors.ExportToast) - ); - await toastElement.waitForDisplayed({ reverse: true }); - - // Confirm that we exported what we expected to export. - const text = await fs.readFile(filename, 'utf-8'); - const lines = text.split(/\r?\n/); - expect(lines.length).to.equal(1); - // We abort before we add the columns to the file. - expect(lines[0]).to.equal(''); - - const exportCompletedEvent = await telemetryEntry('Export Completed'); - delete exportCompletedEvent.duration; // Duration varies. - deleteCommonVariedProperties(exportCompletedEvent); - expect(exportCompletedEvent).to.deep.equal({ - all_docs: false, - file_type: 'csv', - field_count: 2, - fields_added_count: 0, - fields_not_selected_count: 1, - field_option: 'select-fields', - number_of_docs: 0, - has_projection: false, - success: true, - type: 'query', - stopped: true, + it('aborts an in progress CSV export when disconnected', async function () { + const telemetryEntry = await browser.listenForTelemetryEvents( + telemetry + ); + + // Set a query that we'll use. + await browser.runFindOperation( + 'Documents', + '{ $where: "function() { sleep(100); return true; }" }' + ); + + // Open the modal. + await browser.clickVisible(Selectors.ExportCollectionMenuButton); + await browser.clickVisible(Selectors.ExportCollectionQueryOption); + await browser.waitForOpenModal(Selectors.ExportModal); + + // Choose to export select fields. + await browser.clickVisible(Selectors.ExportQuerySelectFieldsOption); + await browser.clickVisible(Selectors.ExportNextStepButton); + + // Click to export the `i` and `j` fields. + await toggleExportFieldCheckbox(browser, 'i'); + await toggleExportFieldCheckbox(browser, 'j'); + + await browser.clickVisible(Selectors.ExportNextStepButton); + + // Select CSV. + await selectExportFileTypeCSV(browser); + + await browser.clickVisible(Selectors.ExportModalExportButton); + + const filename = outputFilename('disconnected-export-test.csv'); + await browser.setExportFilename(filename); + + // Wait for the modal to go away. + await browser.waitForOpenModal(Selectors.ExportModal, { + reverse: true, + }); + + // Wait for the export to start. + const exportAbortButton = browser.$(Selectors.ExportToastAbort); + await exportAbortButton.waitForDisplayed(); + + await browser.disconnectAll({ closeToasts: false }); + + // Wait for the aborted toast to appear. + const toastElement = browser.$(Selectors.ExportToast); + await toastElement.waitForDisplayed(); + await browser + .$(Selectors.closeToastButton(Selectors.ExportToast)) + .waitForDisplayed(); + + // Check it displays that the export was aborted. + const toastText = await toastElement.getText(); + try { + expect(toastText).to.include('Export aborted'); + } catch (err) { + console.log(toastText); + throw err; + } + + // Close the toast. + await browser.clickVisible( + Selectors.closeToastButton(Selectors.ExportToast) + ); + await toastElement.waitForDisplayed({ reverse: true }); + + // Confirm that we exported what we expected to export. + const text = await fs.readFile(filename, 'utf-8'); + const lines = text.split(/\r?\n/); + expect(lines.length).to.equal(1); + // We abort before we add the columns to the file. + expect(lines[0]).to.equal(''); + + const exportCompletedEvent = await telemetryEntry('Export Completed'); + delete exportCompletedEvent.duration; // Duration varies. + deleteCommonVariedProperties(exportCompletedEvent); + expect(exportCompletedEvent).to.deep.equal({ + all_docs: false, + file_type: 'csv', + field_count: 2, + fields_added_count: 0, + fields_not_selected_count: 1, + field_option: 'select-fields', + number_of_docs: 0, + has_projection: false, + success: true, + type: 'query', + stopped: true, + }); + expect(telemetry.screens()).to.include('export_modal'); }); - expect(telemetry.screens()).to.include('export_modal'); }); }); diff --git a/packages/compass-e2e-tests/tests/collection-heading.test.ts b/packages/compass-e2e-tests/tests/collection-heading.test.ts index c00d0b48c4d..ca16fe0ba62 100644 --- a/packages/compass-e2e-tests/tests/collection-heading.test.ts +++ b/packages/compass-e2e-tests/tests/collection-heading.test.ts @@ -8,7 +8,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; describe('Collection heading', function () { let compass: Compass; diff --git a/packages/compass-e2e-tests/tests/collection-import.test.ts b/packages/compass-e2e-tests/tests/collection-import.test.ts index b818d996426..49af622a13c 100644 --- a/packages/compass-e2e-tests/tests/collection-import.test.ts +++ b/packages/compass-e2e-tests/tests/collection-import.test.ts @@ -21,7 +21,7 @@ import type { Telemetry } from '../helpers/telemetry'; import { createDummyCollections, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; const { expect } = chai; diff --git a/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts b/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts index 026af3fec67..5bcc46836a2 100644 --- a/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-indexes-tab.test.ts @@ -10,7 +10,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; const { expect } = chai; diff --git a/packages/compass-e2e-tests/tests/collection-rename.test.ts b/packages/compass-e2e-tests/tests/collection-rename.test.ts index aa51c161675..f71be0eaec1 100644 --- a/packages/compass-e2e-tests/tests/collection-rename.test.ts +++ b/packages/compass-e2e-tests/tests/collection-rename.test.ts @@ -8,7 +8,7 @@ import { getDefaultConnectionNames, } from '../helpers/compass'; import type { CompassBrowser } from '../helpers/compass-browser'; -import { createDummyCollections } from '../helpers/insert-data'; +import { createDummyCollections } from '../helpers/mongo-clients'; import * as Selectors from '../helpers/selectors'; const databaseName = 'test'; diff --git a/packages/compass-e2e-tests/tests/collection-schema-tab.test.ts b/packages/compass-e2e-tests/tests/collection-schema-tab.test.ts index a9ed2ac15e0..90d84a037f8 100644 --- a/packages/compass-e2e-tests/tests/collection-schema-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-schema-tab.test.ts @@ -12,7 +12,7 @@ import * as Selectors from '../helpers/selectors'; import { createGeospatialCollection, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; import { cleanUpDownloadedFile, waitForFileDownload, diff --git a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts index ab242df2b66..011ea23005d 100644 --- a/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts +++ b/packages/compass-e2e-tests/tests/collection-validation-tab.test.ts @@ -7,7 +7,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import { expect } from 'chai'; import { isTestingDesktop } from '../helpers/test-runner-context'; diff --git a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts index e5ead02073c..aa0a16df838 100644 --- a/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts +++ b/packages/compass-e2e-tests/tests/data-modeling-tab.test.ts @@ -11,7 +11,7 @@ import * as Selectors from '../helpers/selectors'; import { createNestedDocumentsCollection, createNumbersStringCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; import { cleanUpDownloadedFile, waitForFileDownload, diff --git a/packages/compass-e2e-tests/tests/database-collections-tab.test.ts b/packages/compass-e2e-tests/tests/database-collections-tab.test.ts index d9d97915c75..b770299e728 100644 --- a/packages/compass-e2e-tests/tests/database-collections-tab.test.ts +++ b/packages/compass-e2e-tests/tests/database-collections-tab.test.ts @@ -14,7 +14,7 @@ import * as Selectors from '../helpers/selectors'; import { createDummyCollections, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; async function waitForCollectionAndBadge( browser: CompassBrowser, diff --git a/packages/compass-e2e-tests/tests/instance-databases-tab.test.ts b/packages/compass-e2e-tests/tests/instance-databases-tab.test.ts index 7bea2706fb0..ac8b5e5beb3 100644 --- a/packages/compass-e2e-tests/tests/instance-databases-tab.test.ts +++ b/packages/compass-e2e-tests/tests/instance-databases-tab.test.ts @@ -13,7 +13,7 @@ import * as Selectors from '../helpers/selectors'; import { createDummyCollections, createNumbersCollection, -} from '../helpers/insert-data'; +} from '../helpers/mongo-clients'; const INITIAL_DATABASE_NAMES = ['admin', 'config', 'local', 'test']; diff --git a/packages/compass-e2e-tests/tests/instance-sidebar.test.ts b/packages/compass-e2e-tests/tests/instance-sidebar.test.ts index 4a6b2caba83..0c765f80a2b 100644 --- a/packages/compass-e2e-tests/tests/instance-sidebar.test.ts +++ b/packages/compass-e2e-tests/tests/instance-sidebar.test.ts @@ -11,7 +11,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; const { expect } = chai; diff --git a/packages/compass-e2e-tests/tests/my-queries-tab.test.ts b/packages/compass-e2e-tests/tests/my-queries-tab.test.ts index b3edacb40a7..eb098f489d8 100644 --- a/packages/compass-e2e-tests/tests/my-queries-tab.test.ts +++ b/packages/compass-e2e-tests/tests/my-queries-tab.test.ts @@ -12,7 +12,7 @@ import { import type { QueryOptions } from '../helpers/commands'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import { MongoClient } from 'mongodb'; import { context as runnerContext } from '../helpers/test-runner-context'; diff --git a/packages/compass-e2e-tests/tests/read-only.test.ts b/packages/compass-e2e-tests/tests/read-only.test.ts index b4b8af79a3f..db1c5d824a8 100644 --- a/packages/compass-e2e-tests/tests/read-only.test.ts +++ b/packages/compass-e2e-tests/tests/read-only.test.ts @@ -6,7 +6,7 @@ import { } from '../helpers/compass'; import { expect } from 'chai'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import type { Compass } from '../helpers/compass'; import type { CompassBrowser } from '../helpers/compass-browser'; import { isTestingWeb } from '../helpers/test-runner-context'; diff --git a/packages/compass-e2e-tests/tests/read-write.test.ts b/packages/compass-e2e-tests/tests/read-write.test.ts index bebb631db3c..4ecfb2dd9cc 100644 --- a/packages/compass-e2e-tests/tests/read-write.test.ts +++ b/packages/compass-e2e-tests/tests/read-write.test.ts @@ -6,7 +6,7 @@ import { } from '../helpers/compass'; import { expect } from 'chai'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import type { Compass } from '../helpers/compass'; import type { CompassBrowser } from '../helpers/compass-browser'; diff --git a/packages/compass-e2e-tests/tests/tabs.test.ts b/packages/compass-e2e-tests/tests/tabs.test.ts index 45a987f89ab..86ad3884a4d 100644 --- a/packages/compass-e2e-tests/tests/tabs.test.ts +++ b/packages/compass-e2e-tests/tests/tabs.test.ts @@ -8,7 +8,7 @@ import { } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; import * as Selectors from '../helpers/selectors'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; import { expect } from 'chai'; describe('Global Tabs', function () { diff --git a/packages/compass-e2e-tests/tests/time-to-first-query.test.ts b/packages/compass-e2e-tests/tests/time-to-first-query.test.ts index c6d6439c708..6a01ca8424d 100644 --- a/packages/compass-e2e-tests/tests/time-to-first-query.test.ts +++ b/packages/compass-e2e-tests/tests/time-to-first-query.test.ts @@ -7,7 +7,7 @@ import { TEST_COMPASS_WEB, } from '../helpers/compass'; import type { Compass } from '../helpers/compass'; -import { createNumbersCollection } from '../helpers/insert-data'; +import { createNumbersCollection } from '../helpers/mongo-clients'; describe('Time to first query', function () { let compass: Compass | undefined; diff --git a/packages/compass-test-server/src/index.ts b/packages/compass-test-server/src/index.ts index f219932521c..0e01d7e27ec 100644 --- a/packages/compass-test-server/src/index.ts +++ b/packages/compass-test-server/src/index.ts @@ -4,7 +4,12 @@ import { createHash } from 'crypto'; import path from 'path'; import os from 'os'; -export type { MongoCluster, MongoClusterOptions } from 'mongodb-runner'; +export type { + MongoCluster, + MongoClusterOptions, + LogEntry, +} from 'mongodb-runner'; +export { ServerLogsChecker, type WarningFilter } from './server-logs-checker'; function hash(input: string): string { return createHash('sha256').update(input).digest('hex').slice(0, 12); diff --git a/packages/compass-test-server/src/server-logs-checker.ts b/packages/compass-test-server/src/server-logs-checker.ts new file mode 100644 index 00000000000..406e213f7c7 --- /dev/null +++ b/packages/compass-test-server/src/server-logs-checker.ts @@ -0,0 +1,131 @@ +import type { MongoClusterEvents, LogEntry } from 'mongodb-runner'; +import { type MongoCluster } from 'mongodb-runner'; + +/** + * Filter for allowing specific server warnings. + * Can be a numeric log ID or a predicate function. + */ +export type WarningFilter = number | ((entry: LogEntry) => boolean); + +const DEFAULT_ALLOWED_WARNINGS: WarningFilter[] = [ + 2658100, // "Hinted index could not provide a bounded scan, reverting to whole index scan" + 4615610, // "Failed to check socket connectivity", + 20526, // "Failed to gather storage statistics for slow operation" + 22225, // "Flow control is engaged and the sustainer point is not moving. Please check the health of all secondaries." + (l: LogEntry) => { + // "Use of deprecated server parameter name" (FTDC) + return (l.id === 636300 || l.id === 23803) && l.context === 'ftdc'; + }, + (l: LogEntry) => { + // "Aggregate command executor error" + return ( + l.id === 23799 && + [ + 'NamespaceNotFound', + 'CommandNotSupportedOnView', // Compass probing views for stats/schema + ].includes(l.attr?.error?.codeName) + ); + }, +]; + +/** + * Monitors MongoDB server logs and validates that no unexpected warnings occur. + * Modeled after the mongosh implementation in PR #2574. + */ +export class ServerLogsChecker { + private collectedWarnings: LogEntry[] = []; + private warningFilters: ((entry: LogEntry) => boolean)[] = []; + private listener: (serverUUID: string, entry: LogEntry) => void; + private cluster: MongoCluster; + + constructor(cluster: MongoCluster) { + this.cluster = cluster; + this.listener = (_serverUUID: string, entry: LogEntry) => { + // Only collect warnings (W), errors (E), and fatal (F) severity logs + // Apply filters at collection time - filtered warnings are never stored + if ( + (entry.severity === 'W' || + entry.severity === 'E' || + entry.severity === 'F') && + !this.warningFilters.some((filter) => filter(entry)) + ) { + this.collectedWarnings.push(entry); + } + }; + + // Add default warning filters + for (const filter of DEFAULT_ALLOWED_WARNINGS) { + this.allowWarning(filter); + } + + // Subscribe to mongoLog events + this.cluster.on( + 'mongoLog', + this.listener as MongoClusterEvents['mongoLog'] extends ( + ...args: infer A + ) => void + ? (...args: A) => void + : never + ); + } + + /** + * Get a copy of the collected warnings. + */ + get warnings(): LogEntry[] { + return [...this.collectedWarnings]; + } + + /** + * Allow a specific warning to pass validation. + * Must be called BEFORE the warning occurs (filters are applied at collection time). + * @param filter - A log ID (number) or predicate function + * @returns A function to unsubscribe this filter + */ + allowWarning(filter: WarningFilter): () => void { + const filterFn = + typeof filter === 'number' + ? (entry: LogEntry) => entry.id === filter + : filter; + + this.warningFilters.push(filterFn); + return () => { + const index = this.warningFilters.indexOf(filterFn); + if (index !== -1) { + this.warningFilters.splice(index, 1); + } + }; + } + + /** + * Check for unexpected warnings and throw if any are found. + * Clears the collected warnings after checking. + */ + noServerWarningsCheckpoint(): void { + const warnings = this.warnings; + this.collectedWarnings = []; + + if (warnings.length > 0) { + const warningDetails = warnings + .map((w) => ` - [${w.severity}] ID:${w.id ?? 'unknown'} ${w.message}`) + .join('\n'); + throw new Error( + `Unexpected server warnings detected:\n${warningDetails}` + ); + } + } + + /** + * Stop listening to log events. + */ + close(): void { + this.cluster.off( + 'mongoLog', + this.listener as MongoClusterEvents['mongoLog'] extends ( + ...args: infer A + ) => void + ? (...args: A) => void + : never + ); + } +}