Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ test-app/containerLogs
test-complete-app/build
test-complete-app/.gradle
test-complete-app-mlDeploy/build
test-complete-app-mlDeploy/.gradle
test-complete-app-mlDeploy/.gradle

# Compiled TypeScript test files
test-typescript/*.js
15 changes: 15 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ def runTypeCheck() {
'''
}

def runTypeScriptTests() {
sh label: 'run-typescript-tests', script: '''
export PATH=${NODE_HOME_DIR}/bin:$PATH
cd node-client-api
npm ci
npm run test:compile
./node_modules/.bin/mocha --timeout 10000 test-typescript/*.js --reporter mocha-junit-reporter --reporter-options mochaFile=$WORKSPACE/test-typescript-reports.xml || true
'''
junit '**/*test-typescript-reports.xml'
}

def runE2ETests() {
sh label: 'run-e2e-tests', script: '''
export PATH=${NODE_HOME_DIR}/bin:$PATH
Expand Down Expand Up @@ -142,6 +153,7 @@ pipeline {
runTypeCheck()
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-12')
runTests()
runTypeScriptTests()
runE2ETests()
}
post {
Expand All @@ -165,6 +177,7 @@ pipeline {
steps {
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-11')
runTests()
runTypeScriptTests()
runE2ETests()
}
post {
Expand All @@ -185,6 +198,7 @@ pipeline {
steps {
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-12')
runTests()
runTypeScriptTests()
runE2ETests()
}
post {
Expand All @@ -205,6 +219,7 @@ pipeline {
steps {
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-10')
runTests()
runTypeScriptTests()
runE2ETests()
}
post {
Expand Down
2 changes: 1 addition & 1 deletion lib/responder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ function operationResultStream() {
function operationErrorListener(error) {
/*jshint validthis:true */
const operation = this;
if(operation.client.connectionParams.apiKey){
if(operation.client.connectionParams && operation.client.connectionParams.apiKey){
if(error.statusCode === 401 && operation.expiration <= (new Date())){
if(!operation.lockAccessToken){
operation.lockAccessToken = true;
Expand Down
19 changes: 17 additions & 2 deletions marklogic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ declare module 'marklogic' {
export interface DatabaseClient {
/**
* Tests if a connection is successful.
* Call .result() to get a promise.
* @since 2.1
* @returns A promise that resolves to an object indicating connection status
* @returns A result provider with a result() method
*/
checkConnection(): Promise<ConnectionCheckResult>;
checkConnection(): ResultProvider<ConnectionCheckResult>;

/**
* Releases the client and destroys the agent.
Expand All @@ -92,6 +93,20 @@ declare module 'marklogic' {
release(): void;
}

/**
* A result provider that wraps asynchronous operations.
* Call .result() to get a Promise for the result.
*/
export interface ResultProvider<T> {
/**
* Gets a promise for the operation result.
* @param onFulfilled - Optional callback for success
* @param onRejected - Optional callback for errors
* @returns A promise that resolves to the result
*/
result(onFulfilled?: (value: T) => void, onRejected?: (reason: any) => void): Promise<T>;
}

/**
* Creates a DatabaseClient object for accessing a database.
* @param config - Configuration for connecting to the database
Expand Down
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"scripts": {
"doc": "jsdoc -c jsdoc.json lib/*.js README.md",
"lint": "gulp lint",
"test:types": "tsc --noEmit"
"test:types": "tsc --noEmit",
"test:compile": "tsc test-typescript/checkConnection-runtime.test.ts",
"pretest:typescript": "npm run test:compile"
},
"keywords": [
"marklogic",
Expand Down Expand Up @@ -53,6 +55,7 @@
},
"devDependencies": {
"@jsdoc/salty": "0.2.9",
"@types/mocha": "10.0.10",
"@types/node": "22.10.1",
"ajv": "8.17.1",
"ast-types": "0.14.2",
Expand Down
86 changes: 77 additions & 9 deletions test-typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,72 @@

This directory contains TypeScript tests to verify that the type definitions in `marklogic.d.ts` work correctly.

## How to Test Types
## Types of Tests

### 1. Compile-Only Tests (Type Checking)
Files like `basic-types.test.ts`, `connection-methods.test.ts`, `type-constraints.test.ts`, `error-examples.test.ts`

- **Purpose**: Verify that TypeScript code compiles without errors
- **Execution**: Not executed at runtime - only compiled
- **Speed**: Very fast (seconds)
- **Requirements**: No MarkLogic server needed
- **Run with**: `npm run test:types`

These tests validate:
- Type definitions are syntactically correct
- Type constraints work (e.g., `authType` only accepts valid values)
- IntelliSense will work for users
- Type errors are caught at compile time

### 2. Runtime Tests
Files like `checkConnection-runtime.test.ts`

Run the type checking with:
- **Purpose**: Verify that TypeScript definitions match actual runtime behavior
- **Execution**: Compiled to JavaScript and executed with mocha
- **Speed**: Slower (requires MarkLogic)
- **Requirements**: MarkLogic server running
- **Run with**: `npm run test:compile && npx mocha test-typescript/*.js`

These tests validate:
- Types compile correctly (compile-time check)
- Real API calls return the expected types (runtime check)
- TypeScript definitions accurately reflect the actual JavaScript behavior

## How to Test Types

### Type Checking Only
```bash
npm run test:types
```

This command runs `tsc --noEmit`, which checks for TypeScript errors without generating JavaScript files.
This runs `tsc --noEmit`, which checks for TypeScript errors without generating JavaScript files.

### Runtime Tests
```bash
npm run test:compile # Compile TypeScript tests to JavaScript
npx mocha test-typescript/*.js # Run compiled tests against MarkLogic
```

Or in one command:
```bash
npm run test:compile && npx mocha test-typescript/*.js
```

## Why Two Approaches?

## Why This Approach?
**Compile-only tests** are great for:
- Fast feedback during development
- Catching type definition errors quickly
- CI/CD pre-flight checks (before spinning up MarkLogic)
- Validating that autocomplete/IntelliSense will work

TypeScript's compiler is the best way to test type definitions because:
- It catches type errors at compile time (before runtime)
- It validates type constraints (like union types for `authType`)
- It ensures IntelliSense and autocomplete will work for users
- It's fast and doesn't require running actual code
**Runtime tests** are essential for:
- Ensuring type definitions match actual behavior
- Catching mismatches between declared types and runtime values
- Integration testing with real MarkLogic instances
- Preventing issues like returning `{}` when a `Promise` was expected

Both approaches complement each other for comprehensive type safety validation.

## Example: Testing for Type Errors

Expand All @@ -40,3 +89,22 @@ error TS2322: Type '"invalid-type"' is not assignable to type 'basic' | 'digest'
```

This confirms your types are working correctly!

## Adding New Tests

### To add a compile-only test:
1. Create a `.test.ts` file in this directory
2. Use `/// <reference path="../marklogic.d.ts" />` to load types
3. Import types with: `type MyType = import('marklogic').MyType;`
4. Write code that should compile (or intentionally fail)
5. Run `npm run test:types` to verify

### To add a runtime test:
1. Create a `.test.ts` file in this directory
2. Use the same reference and import pattern as above
3. Import test framework: `import should = require('should');`
4. Use `describe`/`it` blocks like normal mocha tests
5. Make actual API calls to MarkLogic
6. Compile with `npm run test:compile` and run with mocha

**Note**: Compiled `.js` files are gitignored and regenerated on each test run.
97 changes: 97 additions & 0 deletions test-typescript/checkConnection-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
*/

/// <reference path="../marklogic.d.ts" />

/**
* TypeScript runtime test to validate that checkConnection returns ResultProvider.
* This test ensures the TypeScript definitions match the actual runtime behavior
* by making real calls to MarkLogic AND verifying types at compile time.
*/

import should = require('should');
const marklogic = require('..');
const testconfig = require('../etc/test-config-qa.js');

const db = marklogic.createDatabaseClient(testconfig.restReaderConnection);

// Type alias for easier reference
type ResultProvider<T> = import('marklogic').ResultProvider<T>;
type ConnectionCheckResult = import('marklogic').ConnectionCheckResult;
type DatabaseClientConfig = import('marklogic').DatabaseClientConfig;

describe('checkConnection ResultProvider validation', function() {

it('should return ResultProvider with .result() method', function(done) {
// This validates that checkConnection returns a ResultProvider
// TypeScript will verify the type at compile time
const resultProvider: ResultProvider<ConnectionCheckResult> = db.checkConnection();

// Verify it has a .result() method (core requirement for ResultProvider)
should(resultProvider).have.property('result');
should(resultProvider.result).be.a.Function();

// Call .result() to get a Promise
const promise: Promise<ConnectionCheckResult> = resultProvider.result();

// Verify .result() returns a Promise (thenable)
should(promise).have.property('then');
should(promise.then).be.a.Function();

// Verify the Promise resolves to ConnectionCheckResult
promise.then((response: ConnectionCheckResult) => {
should(response).have.property('connected');
should(response.connected).be.a.Boolean();

if (response.connected === true) {
done();
} else {
done(new Error('Expected connection to succeed but got: ' + JSON.stringify(response)));
}
}).catch(done);
});

it('should work with async/await pattern', async function() {
// TypeScript verifies the return type matches ConnectionCheckResult
const result: ConnectionCheckResult = await db.checkConnection().result();

// Verify result shape matches ConnectionCheckResult
should(result).have.property('connected');
should(result.connected).be.a.Boolean();
should(result.connected).equal(true);
});

it('should have error properties when connection fails', function(done) {
// Test with wrong password to get a failed connection
const config: DatabaseClientConfig = {
host: testconfig.restReaderConnection.host,
user: testconfig.restReaderConnection.user,
password: 'wrongpassword', // Invalid password
port: testconfig.restReaderConnection.port,
authType: testconfig.restReaderConnection.authType
};
const db1 = marklogic.createDatabaseClient(config);

db1.checkConnection().result().then((response: ConnectionCheckResult) => {
should(response).have.property('connected');
should(response.connected).be.a.Boolean();

if (response.connected === false) {
// When connected is false, optional error properties should exist
should(response).have.property('httpStatusCode');
should(response.httpStatusCode).be.a.Number();
should(response).have.property('httpStatusMessage');
should(response.httpStatusMessage).be.a.String();
}

db1.release();
done();
}).catch(done);
});

after(function(done) {
db.release();
done();
});
});
Loading
Loading