From d9a236adf101c5da07a2180bed36496e7c88f4e8 Mon Sep 17 00:00:00 2001 From: Steve Biondi Date: Mon, 13 Oct 2025 17:02:07 -0700 Subject: [PATCH 1/2] MLE-24722 - make sure the done() method called within each test once and only once. Set reasonable timeouts for some of the bulk operations and remove infinite timeouts which can hang the test. --- test-basic/annTopK.js | 1 + test-basic/basePath-test.js | 20 +++- test-basic/bindingFromParam.js | 81 +++++++++---- test-basic/client.js | 22 +++- test-basic/cloud_authentication-test.js | 1 + test-basic/digestauth-fips-nomd5load.js | 6 +- test-basic/docColTypes-test.js | 1 + test-basic/documents-core.js | 110 ++++++++++++------ .../documents-data-movement-queryAll.js | 8 +- test-basic/documents-data-movement-readAll.js | 7 +- test-basic/endpoint-caller.js | 1 + test-basic/service-caller.js | 1 + 12 files changed, 178 insertions(+), 81 deletions(-) diff --git a/test-basic/annTopK.js b/test-basic/annTopK.js index e8cc3d87..08910a68 100644 --- a/test-basic/annTopK.js +++ b/test-basic/annTopK.js @@ -89,6 +89,7 @@ describe('tests for annTopK', function () { planAnnTopKOptionsMap) .orderBy(p.col('name')) ); + done(new Error('Expecting an error to be thrown due to invalid key in options argument')); } catch(error){ assert(error.message.toString().includes('options argument at 4 of PlanModifyPlan.annTopK() has invalid key- invalid')) done(); diff --git a/test-basic/basePath-test.js b/test-basic/basePath-test.js index 5ef7d2ca..744b6363 100644 --- a/test-basic/basePath-test.js +++ b/test-basic/basePath-test.js @@ -17,7 +17,9 @@ describe('basePath tests', function() { testconfig.restWriterConnectionWithBasePath.basePath = 'invalid'; const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnectionWithBasePath); dbWriter.documents.write(writeObject) - .result(function(response){}) + .result(function(response){ + done(new Error('Expecting an error to be thrown due to invalid basePath')); + }) .catch(err=> { assert(err.toString().includes('path: invalid/v1/documents')); @@ -29,7 +31,9 @@ describe('basePath tests', function() { testconfig.restWriterConnectionWithBasePath.basePath = '/invalid'; const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnectionWithBasePath); dbWriter.documents.write(writeObject) - .result(function(response){}) + .result(function(response){ + done(new Error('Expecting an error to be thrown due to invalid basePath with a leading slash')); + }) .catch(err=> { assert(err.toString().includes('path: /invalid/v1/documents')); @@ -41,7 +45,9 @@ describe('basePath tests', function() { testconfig.restWriterConnectionWithBasePath.basePath = 'invalid/'; const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnectionWithBasePath); dbWriter.documents.write(writeObject) - .result(function(response){}) + .result(function(response){ + done(new Error('Expecting an error to be thrown due to invalid basePath with a trailing slash')); + }) .catch(err=> { assert(err.toString().includes('path: invalid/v1/documents')); @@ -53,7 +59,9 @@ describe('basePath tests', function() { testconfig.restWriterConnectionWithBasePath.basePath = '/invalid/'; const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnectionWithBasePath); dbWriter.documents.write(writeObject) - .result(function(response){}) + .result(function(response){ + done(new Error('Expecting an error to be thrown due to invalid basePath with starting and trailing slashes')); + }) .catch(err=> { assert(err.toString().includes('path: /invalid/v1/documents')); @@ -65,7 +73,9 @@ describe('basePath tests', function() { testconfig.restWriterConnectionWithBasePath.basePath = '//invalid//'; const dbWriter = marklogic.createDatabaseClient(testconfig.restWriterConnectionWithBasePath); dbWriter.documents.write(writeObject) - .result(function(response){}) + .result(function(response){ + done(new Error('Expecting an error to be thrown due to invalid basePath with multiple starting and trailing slashes')); + }) .catch(err=> { try{ diff --git a/test-basic/bindingFromParam.js b/test-basic/bindingFromParam.js index 8c368df5..ab9da704 100644 --- a/test-basic/bindingFromParam.js +++ b/test-basic/bindingFromParam.js @@ -111,6 +111,7 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', 'qualifier', outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate,null, temp); + done(new Error('Expecting an error to be thrown due to invalid row-col-types argument')); } catch (e) { e.toString().should.equal('Error: row-col-types argument at 2 of PlanBuilder.fromParam() has invalid argument for PlanRowColTypes value: [object Object]'); done(); @@ -157,6 +158,7 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', 'qualifier', outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate, null, temp); + done(new Error('Expecting an error to be thrown due to invalid row-col-types argument')); } catch (e) { e.toString().should.equal('Error: row-col-types argument at 2 of PlanBuilder.fromParam() has another type than string'); done(); @@ -205,7 +207,11 @@ describe('optic-update fromParam tests', function(){ }, {"column": "lastName", "type": "string"}]; const planBuilderTemplate = op.fromParam('myDocs', 'qualifier', outputCols); const temp = {myDocs: rows}; - db.rows.query(planBuilderTemplate, null, temp).catch(e => { + db.rows.query(planBuilderTemplate, null, temp) + .then(function(response){ + done(new Error('Expecting an error to be thrown due to null value for non-nullable column')); + }) + .catch(e => { e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); done(); }); @@ -221,7 +227,11 @@ describe('optic-update fromParam tests', function(){ }, {"column": "lastName", "type": "string", "nullable": true}]; const planBuilderTemplate = op.fromParam('myDocs', 'qualifier', outputCols); const temp = {myDocs: rows}; - db.rows.query(planBuilderTemplate, null,temp).catch(e => { + db.rows.query(planBuilderTemplate, null,temp) + .then(function(response){ + done(new Error('Expecting an error to be thrown due to invalid row-col-types argument')); + }) + .catch(e => { e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); done(); }); @@ -237,7 +247,11 @@ describe('optic-update fromParam tests', function(){ }, {"column": "lastName", "type": "string", "nullable": true}]; const planBuilderTemplate = op.fromParam('myDocs', 'qualifier', outputCols); const temp = {myDocs: rows}; - db.rows.query(planBuilderTemplate,null, temp).catch(e => { + db.rows.query(planBuilderTemplate,null, temp) + .then(function(response){ + done(new Error('Expecting an error to be thrown due to invalid row-col-types argument')); + }) + .catch(e => { e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); done(); }); @@ -255,10 +269,13 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', null, outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate,null, temp) - .catch(e => { - e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); - done(); - }); + .then(function(response){ + done(new Error('Expecting an error to be thrown due to null value for non-nullable column')); + }) + .catch(e => { + e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); + done(); + }); } catch (e) { done(); } @@ -274,10 +291,13 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', null, outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate, null,temp) - .catch(e => { - e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); - done(); - }); + .then(function(response){ + done(new Error('Expecting an error to be thrown due to extra non-defined column types')); + }) + .catch(e => { + e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); + done(); + }); }); @@ -290,10 +310,13 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', null, outputCols); const temp = {bindingParam: rows}; db.rows.query(planBuilderTemplate, null, temp) - .catch(e => { - e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); - done(); - }); + .then(function(response){ + done(new Error('Expecting an error to be thrown due to non-consistent binding argument name')); + }) + .catch(e => { + e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); + done(); + }); }); @@ -311,10 +334,13 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', null, outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate, null, temp) - .catch(e => { - e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); - done(); - }); + .then(function(response){ + done(new Error('Expecting an error to be thrown due to mismatch type')); + }) + .catch(e => { + e.toString().includes('Error: binding arguments /v1/rows: cannot process response with 500 status'); + done(); + }); }); @@ -333,6 +359,7 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', 1234, outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate, null, temp); + done(new Error('Expecting an error to be thrown due to invalid qualifier argument')); } catch (e) { e.toString().includes('Error: qualifier argument at 1 of PlanBuilder.fromParam() must be a XsString value'); done(); @@ -353,11 +380,17 @@ describe('optic-update fromParam tests', function(){ const planBuilderTemplate = op.fromParam('myDocs', null, outputCols); const temp = {myDocs: rows}; db.rows.query(planBuilderTemplate, null, temp).then(res => { - const rows = res.rows; - rows[0].id.value.should.equal(1); - rows[0].firstName.value.should.equal("firstName_1"); - rows[0].lastName.value.should.equal("lastName_1"); - done(); + try { + const rows = res.rows; + rows[0].id.value.should.equal(1); + rows[0].firstName.value.should.equal("firstName_1"); + rows[0].lastName.value.should.equal("lastName_1"); + done(); + } catch (e) { + done(e); + } + }).catch(e => { + done(e); }); }); diff --git a/test-basic/client.js b/test-basic/client.js index a484b315..a26fcf05 100644 --- a/test-basic/client.js +++ b/test-basic/client.js @@ -87,13 +87,21 @@ describe('database clients', function () { done(); }); it('should use a custom agent', function (done) { - agentDb.connectionParams.agent.options.keepAliveTimeoutMsecs.should.equal(1000); - done(); + try { + agentDb.connectionParams.agent.options.keepAliveTimeoutMsecs.should.equal(1000); + done(); + } catch(e){ + done(e); + } }); it('should create a timestamp', function (done) { let timestamp = db.createTimestamp('123'); - timestamp.value.should.equal('123'); - done(); + try { + timestamp.value.should.equal('123'); + done(); + } catch(e){ + done(e); + } }); it('should throw Error when server expects DIGEST and authType is CERTIFICATE', function (done) { const db = marklogic.createDatabaseClient({ @@ -108,6 +116,7 @@ describe('database clients', function () { .result(function (documents) { documents.forEach(function (document) { }); + done(new Error('Expecting an error to be thrown due to invalid authentication configuration')); }) .catch(error => { assert(error.toString().includes('response with invalid 401 status with path: /v1/search')); @@ -143,7 +152,9 @@ describe('database clients', function () { contentType: 'application/json', content: '{"key1":"value 1"}' }) - .result() + .result(function (document) { + done(new Error('Expecting an error to be thrown due to invalid SSL configuration')); + }) .catch(error => { try{ assert(error.message.toString().includes('You have attempted to access an HTTP server using HTTPS. Please check your configuration.') || @@ -158,6 +169,7 @@ describe('database clients', function () { it('should throw error when authType is OAuth and oauthToken is missing', function(done){ try { marklogic.createDatabaseClient(testconfig.restConnectionForOauth); + done(new Error('Expecting an error to be thrown due to missing oauthToken')); } catch(error){ assert(error.message.toString().includes('oauthToken required for OAuth authentication. ')); done(); diff --git a/test-basic/cloud_authentication-test.js b/test-basic/cloud_authentication-test.js index 01bcd333..86edbe66 100644 --- a/test-basic/cloud_authentication-test.js +++ b/test-basic/cloud_authentication-test.js @@ -15,6 +15,7 @@ describe('cloud-authentication tests', function() { host: 'invalid', authType: 'cloud' }); + done(new Error('Expecting an error to be thrown due to missing apiKey')); } catch(error) { assert(error.toString().includes('apiKey needed for MarkLogic cloud authentication.')); done(); diff --git a/test-basic/digestauth-fips-nomd5load.js b/test-basic/digestauth-fips-nomd5load.js index 586b9b21..e0192fec 100644 --- a/test-basic/digestauth-fips-nomd5load.js +++ b/test-basic/digestauth-fips-nomd5load.js @@ -6,7 +6,7 @@ const should = require('should'); describe('FIPS test - ensure MD5 hash digester object is not loaded by default on require of www-authenticate module', function () { - it('should not automatically load MD5 digest algorithm function when requiring www-authenticate module', function () { + it('should not automatically load MD5 digest algorithm function when requiring www-authenticate module', function (done) { /** * Attempt to load/require the www-authenticate module after applying a monkey-patch * to the crypto.createHash function to intercept any attempts to create an MD5 hash @@ -42,7 +42,9 @@ describe('FIPS test - ensure MD5 hash digester object is not loaded by default o // Require the module - should not call to get MD5 digester so should not throw (() => require('../lib/www-authenticate-patched/md5')).should.not.throw(); (() => require('../lib/www-authenticate-patched/www-authenticate')).should.not.throw(); - + done(); + } catch (e) { + done(e); } finally { // Restore the original createHash function to avoid side effects // This MUST execute to avoid breaking other tests! diff --git a/test-basic/docColTypes-test.js b/test-basic/docColTypes-test.js index c2880f75..70bd4997 100644 --- a/test-basic/docColTypes-test.js +++ b/test-basic/docColTypes-test.js @@ -116,6 +116,7 @@ describe('optic-update docColTypes tests', function() { try { db.rows.query(op.fromDocDescriptors(docsDescriptor).write(op.docColTypes())); + done(new Error('Expecting an error to be thrown due to invalid document descriptor')); } catch (e) { e.toString().includes('Error: doc-cols argument at 0 of PlanModifyPlan.write() must have type PlanDocColsIdentifier'); done(); diff --git a/test-basic/documents-core.js b/test-basic/documents-core.js index 4b6bc187..fc8ccf04 100644 --- a/test-basic/documents-core.js +++ b/test-basic/documents-core.js @@ -58,7 +58,7 @@ describe('document content', function(){ document.content.key1.should.equal('value 1'); done(); }) - .catch(error=> done(error)); + .catch(done); }); it('should read back normal contents with enableGzippedResponses as false', function(done){ @@ -379,18 +379,21 @@ describe('document content', function(){ '/test/write/arrayObject2.json' ) .result(function(documents) { - valcheck.isUndefined(documents).should.equal(false); - documents.length.should.equal(2); - for (var i=0; i < 2; i++) { - var document = documents[i]; - valcheck.isUndefined(document).should.equal(false); - document.should.have.property('content'); - document.content.should.have.property('key1'); - document.content.key1.should.equal('value 1'); + try { + valcheck.isUndefined(documents).should.equal(false); + documents.length.should.equal(2); + for (var i=0; i < 2; i++) { + var document = documents[i]; + valcheck.isUndefined(document).should.equal(false); + document.should.have.property('content'); + document.content.should.have.property('key1'); + document.content.key1.should.equal('value 1'); + } + done(); + } catch (e) { + done(e); } - done(); - }) - .catch(done); + }) }); it('should read as an object stream with content and metadata', function(done){ var count = 0; @@ -399,15 +402,24 @@ describe('document content', function(){ categories: ['content', 'quality'] }).stream('object').on('error', done). on('data', function (data) { - count++; - valcheck.isObject(data).should.equal(true); - data.should.have.property('content'); - data.should.have.property('quality'); + try { + count++; + valcheck.isObject(data).should.equal(true); + data.should.have.property('content'); + data.should.have.property('quality'); + } catch (e) { + done(e); + return; + } }). on('end', function () { - count.should.equal(2); - done(); - }); + try { + count.should.equal(2); + done(); + } catch (e) { + done(e); + } + }) }); it('should read as an object stream with content only', function(done){ var count = 0; @@ -416,14 +428,23 @@ describe('document content', function(){ categories: ['content'] }).stream('object').on('error', done). on('data', function (data) { - count++; - valcheck.isObject(data).should.equal(true); - data.should.have.property('content'); - data.should.not.have.property('quality'); + try { + count++; + valcheck.isObject(data).should.equal(true); + data.should.have.property('content'); + data.should.not.have.property('quality'); + } catch (e) { + done(e); + return; + } }). on('end', function () { - count.should.equal(2); - done(); + try { + count.should.equal(2); + done(); + } catch (e) { + done(e); + } }); }); it('should read as an object stream with metadata only', function(done){ @@ -433,14 +454,23 @@ describe('document content', function(){ categories: ['quality'] }).stream('object').on('error', done). on('data', function (data) { - count++; - valcheck.isObject(data).should.equal(true); - data.should.not.have.property('content'); - data.should.have.property('quality'); + try { + count++; + valcheck.isObject(data).should.equal(true); + data.should.not.have.property('content'); + data.should.have.property('quality'); + } catch (e) { + done(e); + return; + } }). on('end', function () { - count.should.equal(2); - done(); + try { + count.should.equal(2); + done(); + } catch (e) { + done(e); + } }); }); }); @@ -459,13 +489,17 @@ describe('document content', function(){ it('should read back the value', function(done){ db.documents.read('/test/write/writable1.json').stream('chunked'). on('data', function(chunk) { - valcheck.isUndefined(chunk).should.equal(false); - var content = JSON.parse(chunk.toString()); - valcheck.isUndefined(content).should.equal(false); - content.should.have.property('key1'); - content.key1.should.equal('value 1'); - done(); - }, done); + try { + valcheck.isUndefined(chunk).should.equal(false); + var content = JSON.parse(chunk.toString()); + valcheck.isUndefined(content).should.equal(false); + content.should.have.property('key1'); + content.key1.should.equal('value 1'); + done(); + } catch (e) { + done(e); + } + }) }); }); diff --git a/test-basic/documents-data-movement-queryAll.js b/test-basic/documents-data-movement-queryAll.js index 21ea68db..cfdaf797 100644 --- a/test-basic/documents-data-movement-queryAll.js +++ b/test-basic/documents-data-movement-queryAll.js @@ -21,9 +21,9 @@ describe('data movement queryAll', function() { before(function (done) { // This "before" and the "after" frequently fail to finish before the timeout triggers // TODO: - // short-term -> run with "timeout 0" and/or change/add "this.timeout(0)" to both methods - // long-term -> Do we need 10000 records for these tests? - this.timeout(0); + // short-term -> add "this.timeout(120000)" to both methods + // long-term -> Do we need 10000 records for these tests? If 120 seconds is not enough, we change the test. + this.timeout(120000); readable = new Stream.Readable({objectMode: true}); uris = []; for(let i=0; i<10000; i++) { @@ -44,7 +44,7 @@ describe('data movement queryAll', function() { }); after((function(done){ - this.timeout(0); + this.timeout(120000); dbWriter.documents.remove(uris) .result(function(response){ done(); diff --git a/test-basic/documents-data-movement-readAll.js b/test-basic/documents-data-movement-readAll.js index b2400d61..3f819866 100644 --- a/test-basic/documents-data-movement-readAll.js +++ b/test-basic/documents-data-movement-readAll.js @@ -23,10 +23,10 @@ let xqyTransformPath = './test-basic/data/flagTransform.xqy'; describe('data movement readAll', function() { // This "before" frequently fails to finish before the timeout triggers // TODO: - // short-term -> run with "timeout 0" and/or change/add "this.timeout(0)" to both methods - // long-term -> Do we need 10000 records for these tests? - this.timeout(120000); + // short-term -> add "this.timeout(120000)" to both methods + // long-term -> Do we need 10000 records for these tests? If 120 seconds is not enough, we change the test. before(function (done) { + this.timeout(120000); let readable = new Stream.Readable({objectMode: true}); for(let i=0; i<10000; i++) { const temp = { @@ -82,6 +82,7 @@ describe('data movement readAll', function() { }); after((function(done){ + this.timeout(120000); categoriesUrisList.forEach(uri=>urisList.push(uri)); dbWriter.documents.remove(urisList) .result(function(){ diff --git a/test-basic/endpoint-caller.js b/test-basic/endpoint-caller.js index 921fd094..aef35c7a 100644 --- a/test-basic/endpoint-caller.js +++ b/test-basic/endpoint-caller.js @@ -12,6 +12,7 @@ let gulpConfig = require('../gulpfile.js'); describe('Endpoint caller', function() { before(function(done){ + this.timeout(30000); // set timeout to 30 seconds, because some of the tests take a while gulpConfig.loadProxyTests(); setTimeout(()=>{done();}, 5000); }); diff --git a/test-basic/service-caller.js b/test-basic/service-caller.js index 1cc7ef11..aee8c50e 100644 --- a/test-basic/service-caller.js +++ b/test-basic/service-caller.js @@ -12,6 +12,7 @@ let gulpConfig = require('../gulpfile.js'); describe('Service caller', function() { before(function(done){ + this.timeout(30000); // set timeout to 30 seconds, because some of the tests take a while gulpConfig.loadProxyTests(); setTimeout(()=>{done();}, 5000); }); From 6fb52f3b16823b7ae1afcf86f284de72230b577f Mon Sep 17 00:00:00 2001 From: Steve Biondi Date: Tue, 14 Oct 2025 17:34:38 -0700 Subject: [PATCH 2/2] More fixes: make sure done() called once and only once for async operations. Change annTopK tests to use await/rejects idiom which is easier to understand. Skip the cloud test that tries to connect to support.beta.marklogic.cloud. That DNS name is not resolving as of today. --- test-basic/annTopK.js | 114 ++++++------- test-basic/cloud_authentication-test.js | 5 +- test-basic/docColTypes-test.js | 1 + .../documents-data-movement-queryAll.js | 14 +- test-basic/documents-data-movement-readAll.js | 156 ++++++++++++------ 5 files changed, 164 insertions(+), 126 deletions(-) diff --git a/test-basic/annTopK.js b/test-basic/annTopK.js index 08910a68..af6e6339 100644 --- a/test-basic/annTopK.js +++ b/test-basic/annTopK.js @@ -13,99 +13,89 @@ let serverConfiguration = {}; const execPlan = pbb.execPlan; describe('tests for annTopK', function () { - this.timeout(5000) - before(function (done) { - try { - testlib.findServerConfiguration(serverConfiguration); - setTimeout(() => { - if (serverConfiguration.serverVersion < 12) { - this.skip(); - } - done(); - }, 3000); - } catch (error) { - done(error); + this.timeout(5000); + before(async function () { + await testlib.findServerConfigurationPromise(serverConfiguration); + + if (serverConfiguration.serverVersion < 12) { + this.skip(); } }); - it('annTopK without PlanAnnTopKOptions', function (done) { - execPlan(p + it('annTopK without PlanAnnTopKOptions', async function () { + const response = await execPlan(p .fromView('vectors', 'persons', '') .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance')) .orderBy(p.col('name')) - ) - .then(function (response) { - verifyResults(response.rows, done); - }) - .catch(error => done(error)); + ); + verifyResults(response.rows); }); - it('annTopK with PlanAnnTopKOptions as a single string', function (done) { - execPlan(p + it('annTopK with PlanAnnTopKOptions as a single string', async function () { + const response = await execPlan(p .fromView('vectors', 'persons', '') .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance'), 'onlyIndex') .orderBy(p.col('name')) - ) - .then(function (response) { - verifyResults(response.rows, done); - }) - .catch(error => done(error)); + ); + verifyResults(response.rows); }); - it('annTopK with PlanAnnTopKOptions as an array of string', function (done) { - execPlan(p + it('annTopK with PlanAnnTopKOptions as an array of string', async function () { + const response = await execPlan(p .fromView('vectors', 'persons', '') .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance'), ['onlyIndex', "maxDistance=0.15", "searchFactor=1.0"]) .orderBy(p.col('name')) - ).then(function (response) { - verifyResults(response.rows, done); - }).catch(error => done(error)); + ); + verifyResults(response.rows); }); - it('annTopK with PlanAnnTopKOptions as a map', function (done) { + it('annTopK with PlanAnnTopKOptions as a map', async function () { const planAnnTopKOptionsMap = new Map(); planAnnTopKOptionsMap.set("maxDistance", 0.158454656600952); planAnnTopKOptionsMap.set("searchFactor", 10.0); - execPlan(p + const response = await execPlan(p .fromView('vectors', 'persons', '') .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance'), planAnnTopKOptionsMap) .orderBy(p.col('name')) - ) - .then(function (response) { - verifyResults(response.rows, done); - }) - .catch(error => done(error)); + ); + verifyResults(response.rows); }); - it('annTopK with invalid PlanAnnTopKOptions', function (done) { + it('annTopK with invalid PlanAnnTopKOptions', async function () { const planAnnTopKOptionsMap = new Map(); planAnnTopKOptionsMap.set('invalid', 10.0); - try{ - execPlan(p - .fromView('vectors', 'persons', '') - .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance'), - planAnnTopKOptionsMap) - .orderBy(p.col('name')) - ); - done(new Error('Expecting an error to be thrown due to invalid key in options argument')); - } catch(error){ - assert(error.message.toString().includes('options argument at 4 of PlanModifyPlan.annTopK() has invalid key- invalid')) - done(); - } + + await assert.rejects( + async () => { + await execPlan(p + .fromView('vectors', 'persons', '') + .annTopK(10, p.col('embedding'), p.vec.vector([1.1, 2.2, 3.3]), p.col('distance'), + planAnnTopKOptionsMap) + .orderBy(p.col('name')) + ); + }, + (error) => { + return error.message.toString().includes('options argument at 4 of PlanModifyPlan.annTopK() has invalid key- invalid'); + } + ); }); - function verifyResults(rows, done){ - try { - assert(rows.length === 2, 'Expecting both rows in the view to be returned.'); - assert(rows[0].name.value === 'Alice'); - assert(rows[0].distance.type === 'xs:float', 'Verifying that the distance column was populated.'); - assert(rows[1].name.value === 'Bob'); - assert(rows[1].distance.type === 'xs:float', 'Verifying that the distance column was populated.'); - done(); - } catch (error){ - done(error) - } + function verifyResults(rows) { + + assert(Array.isArray(rows), 'Expected rows to be an array'); + assert(rows.length === 2, 'Expecting both rows in the view to be returned.'); + assert(rows[0].name.value === 'Alice'); + assert(rows[0].distance.type === 'xs:float', 'Verifying that the distance column was populated.'); + assert(rows[1].name.value === 'Bob'); + assert(rows[1].distance.type === 'xs:float', 'Verifying that the distance column was populated.'); + + // Verify each row has the expected structure + rows.forEach((row, index) => { + assert(row.name && row.name.value, `Row ${index} should have a name with a value`); + assert(row.distance && row.distance.type === 'xs:float', + `Row ${index} should have a distance column of type xs:float`); + }); } }); \ No newline at end of file diff --git a/test-basic/cloud_authentication-test.js b/test-basic/cloud_authentication-test.js index 86edbe66..8eb8f75e 100644 --- a/test-basic/cloud_authentication-test.js +++ b/test-basic/cloud_authentication-test.js @@ -36,7 +36,9 @@ describe('cloud-authentication tests', function() { } }); - it('should throw error with invalid apiKey.', function (done) { + // skip for now, support.beta.marklogic.cloud is not working for me. Not sure if this should be in test suite anyway. + it.skip('should throw error with invalid apiKey.', function (done) { + this.timeout(10000); let db = marklogic.createDatabaseClient({ host: 'support.beta.marklogic.cloud', authType: 'cloud', @@ -47,7 +49,6 @@ describe('cloud-authentication tests', function() { try { // Also verified that it throws 'Error: User's API Key is expired.' when API key has expired a few seconds ago. expect(()=>db.documents.write(writeObject).throws(Error('API Key is not valid.'))); - done(); } catch (error) { done(error); } diff --git a/test-basic/docColTypes-test.js b/test-basic/docColTypes-test.js index 70bd4997..4e01ab7b 100644 --- a/test-basic/docColTypes-test.js +++ b/test-basic/docColTypes-test.js @@ -46,6 +46,7 @@ describe('optic-update docColTypes tests', function() { const plan = op.fromParam('bindingParam', null, op.docColTypes(op.col('uri'))); const temp = {bindingParam: rows}; db.rows.query(plan, null, temp); + done(new Error("Expected an error to be thrown due to only 1 argument to fromParam")); } catch (e) { e.toString().includes('Error: PlanBuilder.docColTypes takes a maximum of 0 arguments but received: 1'); done(); diff --git a/test-basic/documents-data-movement-queryAll.js b/test-basic/documents-data-movement-queryAll.js index cfdaf797..02af3de5 100644 --- a/test-basic/documents-data-movement-queryAll.js +++ b/test-basic/documents-data-movement-queryAll.js @@ -49,7 +49,6 @@ describe('data movement queryAll', function() { .result(function(response){ done(); }) - .catch(err=> done(err)) .catch(done); })); @@ -57,6 +56,7 @@ describe('data movement queryAll', function() { try{ const query = q.directory('/test/dataMovement/requests/queryAll/'); dbWriter.documents.queryAll(query); + done(new Error('Expected an error to be thrown because query is not a cts query.')); } catch(err){ err.toString().should.equal('Error: Query needs to be a cts query.'); done(); @@ -94,12 +94,12 @@ describe('data movement queryAll', function() { }); it('queryAll should throw error if no query is provided', function (done){ - try{ dbWriter.documents.queryAll(); + return done(new Error('Expected an error to be thrown because no query was provided')); } catch(err){ err.toString().should.equal('Error: Query cannot be null or undefined.'); - done(); + return done(); } }); @@ -118,14 +118,14 @@ describe('data movement queryAll', function() { }); it('queryAll should throw error with batchSize=100001', function (done){ - try{ dbWriter.documents.queryAll(query, { batchSize:100001 }); + return done(new Error('Expected an error to be thrown because batchSize greater than 100000')); } catch(err){ err.toString().should.equal('Error: batchSize cannot be greater than 100000'); - done(); + return done(); } }); @@ -259,14 +259,14 @@ describe('data movement queryAll', function() { }); it('queryAll should throw error with consistentSnapshot as Integer', function (done){ - try{ dbWriter.documents.queryAll(query, { consistentSnapshot: 1 }); + return done(new Error('Expected an error to be thrown because consistentSnapshot is invalid')); } catch(err){ err.toString().should.equal('Error: consistentSnapshot needs to be a boolean or DatabaseClient.Timestamp object.'); - done(); + return done(); } }); }); diff --git a/test-basic/documents-data-movement-readAll.js b/test-basic/documents-data-movement-readAll.js index 3f819866..e543b769 100644 --- a/test-basic/documents-data-movement-readAll.js +++ b/test-basic/documents-data-movement-readAll.js @@ -12,6 +12,9 @@ const streamToArray = require('stream-to-array'); const fs = require('fs'); const expect = require('chai').expect; +const TOTAL_NUM_DOCS = 10000; +const CATEGORIES_NUM_DOCS = 400; + let uriStream = new Stream.PassThrough({objectMode: true}); let urisList = []; let result = new Set(); @@ -28,7 +31,7 @@ describe('data movement readAll', function() { before(function (done) { this.timeout(120000); let readable = new Stream.Readable({objectMode: true}); - for(let i=0; i<10000; i++) { + for(let i=0; i { readable = new Stream.Readable({objectMode: true}); - for (let i = 0; i < 400; i++) { + for (let i = 0; i < CATEGORIES_NUM_DOCS; i++) { const temp = { uri: '/test/dataMovement/requests/categories/' + i + '.json', contentType: 'application/json', @@ -74,11 +77,10 @@ describe('data movement readAll', function() { }); }); - beforeEach(function(done){ + beforeEach(function(){ uriStream = new Stream.PassThrough({objectMode: true}); urisList.forEach(uri => uriStream.push(uri)); uriStream.push(null); - done(); }); after((function(done){ @@ -86,15 +88,13 @@ describe('data movement readAll', function() { categoriesUrisList.forEach(uri=>urisList.push(uri)); dbWriter.documents.remove(urisList) .result(function(){ - restAdminDB.config.transforms.remove(xqyTransformName); - done(); + restAdminDB.config.transforms.remove(xqyTransformName) + .result(() => done(), (err) => done(err)); }) - .catch(err=> done(err)) .catch(done); })); it('should readAll documents with empty options', function(done){ - streamToArray(dbWriter.documents.readAll(uriStream), function(err, arr ) { if(err){ @@ -102,11 +102,11 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should readAll documents with onCompletion option', function(done){ - streamToArray(dbWriter.documents.readAll(uriStream,{ onCompletion: ((summary) => summaryValue = summary) }), @@ -116,12 +116,14 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkSummary(summaryValue, done); - }); + }) + .catch(done); }); it('should throw error with invalid inputKind option', function(done){ try{ dbWriter.documents.readAll(uriStream,{inputKind:10}); + done(new Error('Expected an error to be thrown due to invalid inputKind option')); } catch(err){ err.toString().should.equal('Error: Invalid value for inputKind. Value must be array or string.'); done(); @@ -138,7 +140,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should readAll documents with inputKind option as array', function(done){ @@ -157,12 +160,14 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should throw error with invalid outputStreamType option', function(done){ try{ dbWriter.documents.readAll(uriStream,{outputStreamType:10}); + done(new Error('Expected an error to be thrown due to invalid outputStreamType option')); } catch(err){ err.toString().should.equal('Error: Invalid value for outputStreamType. Value must be chunked or object.'); done(); @@ -175,6 +180,7 @@ describe('data movement readAll', function() { outputStreamType: 'chunked', categories: [] }); + done(new Error('Expected an error to be thrown due to invalid categories and outputStreamType as chunked')); } catch(err){ err.toString().should.equal('Error: categories not expected when outputStreamType is chunked.'); done(); @@ -191,7 +197,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should readAll documents with outputStreamType option as chunked', function(done){ @@ -204,15 +211,17 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.toString())); - result.size.should.equal(10000); + result.size.should.equal(TOTAL_NUM_DOCS); result.clear(); done(); - }); + }) + .catch(done); }); it('should throw error with invalid batchSize option', function(done){ try{ dbWriter.documents.readAll(uriStream,{batchSize:-1}); + done(new Error('Expected an error to be thrown due to invalid batchSize')); } catch(err){ err.toString().should.equal('Error: Invalid batchSize. batchSize cannot be less than or equal to 0.'); done(); @@ -229,12 +238,14 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should throw error with invalid batchSize and inputKind as array option', function(done){ try{ dbWriter.documents.readAll(uriStream,{batchSize:10, inputKind:'array'}); + done(new Error('Expected an error to be thrown due to invalid batchSize and inputKind as array')); } catch(err){ err.toString().should.equal('Error: batchSize not expected when inputKind is array.'); done(); @@ -244,6 +255,7 @@ describe('data movement readAll', function() { it('should throw error with invalid concurrentRequests option', function(done){ try{ dbWriter.documents.readAll(uriStream,{concurrentRequests: {multipleOf: 'invalid', multiplier: 4}}); + done(new Error('Expected an error to be thrown due to invalid concurrentRequests option')); } catch(err){ err.toString().should.equal('Error: Invalid value for multipleOf. Value must be forests or hosts.'); done(); @@ -260,7 +272,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should readAll documents with categories option', function(done){ @@ -277,7 +290,8 @@ describe('data movement readAll', function() { } arr.forEach(item => result.add(item)); checkCategoriesResult(result, done); - }); + }) + .catch(done); }); it('should readAll documents with transform option', function(done){ @@ -294,9 +308,10 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item)); checkTransformResult(done); - }); + }) + .catch(done); }) - .catch(err=> done(err)); + .catch(done); }); it('should throw error with invalid onBatchError option', function(done){ @@ -317,13 +332,13 @@ describe('data movement readAll', function() { done(); }); }) - .catch(err=> done(err)); + .catch(done); }); it('should readAll documents with onBatchError option returning null', function(done){ restAdminDB.config.transforms.write(xqyTransformName, 'xquery', fs.createReadStream(xqyTransformPath)) .result(function(response){ - dbWriter.documents.readAll(uriStream,{ + const readStream = dbWriter.documents.readAll(uriStream,{ batchSize:1000, transform: ['tested1'], onBatchError: ((progressSoFar, documents, error) => { @@ -334,14 +349,22 @@ describe('data movement readAll', function() { return null; }), onCompletion: ((summary) => { - summary.docsReadSuccessfully.should.be.equal(0); - summary.docsFailedToBeRead.should.be.equal(10000); - summary.timeElapsed.should.be.greaterThanOrEqual(0); - done(); + try { + summary.docsReadSuccessfully.should.be.equal(0); + summary.docsFailedToBeRead.should.be.equal(TOTAL_NUM_DOCS); + summary.timeElapsed.should.be.greaterThanOrEqual(0); + done(); + } catch (error) { + done(error); + } }) }); + + readStream.on('error', function(err){ + done(err); + }); }) - .catch(err=> done(err)); + .catch(done); }); it('should readAll documents with onBatchError option returning replacement batch', function(done){ @@ -371,30 +394,42 @@ describe('data movement readAll', function() { it('should readAll documents with consistentSnapshot option as true', function(done){ this.timeout(15000); - dbWriter.documents.readAll(uriStream,{ + const readStream = dbWriter.documents.readAll(uriStream,{ consistentSnapshot:true, onCompletion: ((summary) => { - summary.docsReadSuccessfully.should.be.equal(10000); - summary.docsFailedToBeRead.should.be.equal(0); - summary.timeElapsed.should.be.greaterThanOrEqual(0); - summary.consistentSnapshotTimestamp.should.be.greaterThanOrEqual(0); - done(); + try { + summary.docsReadSuccessfully.should.be.equal(TOTAL_NUM_DOCS); + summary.docsFailedToBeRead.should.be.equal(0); + summary.timeElapsed.should.be.greaterThanOrEqual(0); + summary.consistentSnapshotTimestamp.should.be.greaterThanOrEqual(0); + done(); + } catch (error) { + done(error); + } }) }); + + readStream.on('error', function(err){ + done(err); + }); }); it('should readAll documents with consistentSnapshot option as DatabaseClient.Timestamp object', function(done){ this.timeout(120000); - dbWriter.documents.readAll(uriStream,{ + const readStream = dbWriter.documents.readAll(uriStream,{ consistentSnapshot:dbWriter.createTimestamp((Date.now()*10000).toString()), onCompletion: ((summary) => { - summary.docsReadSuccessfully.should.be.equal(10000); + summary.docsReadSuccessfully.should.be.equal(TOTAL_NUM_DOCS); summary.docsFailedToBeRead.should.be.equal(0); summary.timeElapsed.should.be.greaterThanOrEqual(0); summary.consistentSnapshotTimestamp.should.be.greaterThanOrEqual(0); done(); }) }); + + readStream.on('error', function(err){ + done(err); + }); }); it('should readAll documents with consistentSnapshot option as false', function(done){ @@ -402,7 +437,7 @@ describe('data movement readAll', function() { dbWriter.documents.readAll(uriStream,{ consistentSnapshot:false, onCompletion: ((summary) => { - summary.docsReadSuccessfully.should.be.equal(10000); + summary.docsReadSuccessfully.should.be.equal(TOTAL_NUM_DOCS); summary.docsFailedToBeRead.should.be.equal(0); summary.timeElapsed.should.be.greaterThanOrEqual(0); expect(summary.consistentSnapshotTimestamp).to.be.undefined; @@ -433,7 +468,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should queryToReadAll documents with onCompletion option', function(done){ @@ -442,7 +478,7 @@ describe('data movement readAll', function() { const query = q.where(ctsQb.cts.directoryQuery('/test/dataMovement/requests/readAll/')); streamToArray(dbWriter.documents.queryToReadAll(query,{ onCompletion:((summary) => { - summary.docsReadSuccessfully.should.be.equal(10000); + summary.docsReadSuccessfully.should.be.equal(TOTAL_NUM_DOCS); summary.docsFailedToBeRead.should.be.equal(0); summary.timeElapsed.should.be.greaterThanOrEqual(0); }) @@ -453,7 +489,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should queryToReadAll documents with onCompletion, consistentSnapshot and onInitialTimestamp options', function(done){ @@ -471,7 +508,7 @@ describe('data movement readAll', function() { onInitialTimestampValue = new Date(timestampValue); }), onCompletion:((summary) => { - summary.docsReadSuccessfully.should.be.equal(10000); + summary.docsReadSuccessfully.should.be.equal(TOTAL_NUM_DOCS); summary.docsFailedToBeRead.should.be.equal(0); summary.timeElapsed.should.be.greaterThanOrEqual(0); summary.consistentSnapshotTimestamp.toString().should.equal(onInitialTimestampValue.toString()); @@ -483,7 +520,8 @@ describe('data movement readAll', function() { } arr.forEach(item=> result.add(item.uri)); checkResult(done); - }); + }) + .catch(done); }); it('should throw error with categories options rawContent and permissions', function(done){ @@ -520,34 +558,42 @@ describe('data movement readAll', function() { }); docCount.should.be.equal(categoriesUrisList.length); done(); - }); + }) + .catch(done); }); it('should return empty with cts wordQuery when no documents are found', function(done) { const ctsqb = marklogic.ctsQueryBuilder; const q = marklogic.queryBuilder; const query = q.where(ctsqb.cts.wordQuery('zero')); - var res = ''; - var chk = ''; + let summaryResult = null; + let receivedData = false; const queryToReadAllStream = dbWriter.documents.queryToReadAll(query,{ onCompletion:((summary) => { - res = summary; + // should never get here since no documents are to be read + summaryResult = summary; }) }); - queryToReadAllStream.on('error', function (err) { throw new Error(err);}); + queryToReadAllStream.on('error', function (err) { + done(err); + }); queryToReadAllStream.on('data', function(chunk){ - chk = chunk; + receivedData = true; }); - queryToReadAllStream.on('end', function(end){ - expect(res).to.be.empty; - expect(chk).to.be.empty; - done(); + queryToReadAllStream.on('end', function() { + try { + expect(summaryResult).to.be.null; + expect(receivedData).to.be.false; + done(); + } catch (error) { + done(error); + } }); }); }); function checkResult(done){ - result.size.should.equal(10000); + result.size.should.equal(TOTAL_NUM_DOCS); for(let i=0; i { item.content.flagParam.should.be.equal('tested1'); });