Skip to content

Commit 74264b0

Browse files
committed
MLE-24194 Fixing checkConnection and better tests
This was way harder than it should have been, but Copilot finally got it done. checkConnection had the wrong return type, so the method didn't actually working in a "real" TS test - and we now have a "real" TS test. See the Copilot-authored README for explanation.
1 parent 55f7d74 commit 74264b0

File tree

15 files changed

+275
-215
lines changed

15 files changed

+275
-215
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ test-app/containerLogs
1818
test-complete-app/build
1919
test-complete-app/.gradle
2020
test-complete-app-mlDeploy/build
21-
test-complete-app-mlDeploy/.gradle
21+
test-complete-app-mlDeploy/.gradle
22+
23+
# Compiled TypeScript test files
24+
test-typescript/*.js

Jenkinsfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ def runTypeCheck() {
7878
'''
7979
}
8080

81+
def runTypeScriptTests() {
82+
sh label: 'run-typescript-tests', script: '''
83+
export PATH=${NODE_HOME_DIR}/bin:$PATH
84+
cd node-client-api
85+
npm ci
86+
npm run test:compile
87+
./node_modules/.bin/mocha --timeout 10000 test-typescript/*.js --reporter mocha-junit-reporter --reporter-options mochaFile=$WORKSPACE/test-typescript-reports.xml || true
88+
'''
89+
junit '**/*test-typescript-reports.xml'
90+
}
91+
8192
def runE2ETests() {
8293
sh label: 'run-e2e-tests', script: '''
8394
export PATH=${NODE_HOME_DIR}/bin:$PATH
@@ -142,6 +153,7 @@ pipeline {
142153
runTypeCheck()
143154
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-12')
144155
runTests()
156+
runTypeScriptTests()
145157
runE2ETests()
146158
}
147159
post {
@@ -165,6 +177,7 @@ pipeline {
165177
steps {
166178
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-11')
167179
runTests()
180+
runTypeScriptTests()
168181
runE2ETests()
169182
}
170183
post {
@@ -185,6 +198,7 @@ pipeline {
185198
steps {
186199
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-12')
187200
runTests()
201+
runTypeScriptTests()
188202
runE2ETests()
189203
}
190204
post {
@@ -205,6 +219,7 @@ pipeline {
205219
steps {
206220
runDockerCompose('ml-docker-db-dev-tierpoint.bed-artifactory.bedford.progress.com/marklogic/marklogic-server-ubi:latest-10')
207221
runTests()
222+
runTypeScriptTests()
208223
runE2ETests()
209224
}
210225
post {

lib/responder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1224,7 +1224,7 @@ function operationResultStream() {
12241224
function operationErrorListener(error) {
12251225
/*jshint validthis:true */
12261226
const operation = this;
1227-
if(operation.client.connectionParams.apiKey){
1227+
if(operation.client.connectionParams && operation.client.connectionParams.apiKey){
12281228
if(error.statusCode === 401 && operation.expiration <= (new Date())){
12291229
if(!operation.lockAccessToken){
12301230
operation.lockAccessToken = true;

marklogic.d.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ declare module 'marklogic' {
7979
export interface DatabaseClient {
8080
/**
8181
* Tests if a connection is successful.
82+
* Call .result() to get a promise.
8283
* @since 2.1
83-
* @returns A promise that resolves to an object indicating connection status
84+
* @returns A result provider with a result() method
8485
*/
85-
checkConnection(): Promise<ConnectionCheckResult>;
86+
checkConnection(): ResultProvider<ConnectionCheckResult>;
8687

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

96+
/**
97+
* A result provider that wraps asynchronous operations.
98+
* Call .result() to get a Promise for the result.
99+
*/
100+
export interface ResultProvider<T> {
101+
/**
102+
* Gets a promise for the operation result.
103+
* @param onFulfilled - Optional callback for success
104+
* @param onRejected - Optional callback for errors
105+
* @returns A promise that resolves to the result
106+
*/
107+
result(onFulfilled?: (value: T) => void, onRejected?: (reason: any) => void): Promise<T>;
108+
}
109+
95110
/**
96111
* Creates a DatabaseClient object for accessing a database.
97112
* @param config - Configuration for connecting to the database

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
"scripts": {
1919
"doc": "jsdoc -c jsdoc.json lib/*.js README.md",
2020
"lint": "gulp lint",
21-
"test:types": "tsc --noEmit"
21+
"test:types": "tsc --noEmit",
22+
"test:compile": "tsc test-typescript/checkConnection-runtime.test.ts",
23+
"pretest:typescript": "npm run test:compile"
2224
},
2325
"keywords": [
2426
"marklogic",
@@ -53,6 +55,7 @@
5355
},
5456
"devDependencies": {
5557
"@jsdoc/salty": "0.2.9",
58+
"@types/mocha": "10.0.10",
5659
"@types/node": "22.10.1",
5760
"ajv": "8.17.1",
5861
"ast-types": "0.14.2",

test-typescript/README.md

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,72 @@
22

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

5-
## How to Test Types
5+
## Types of Tests
6+
7+
### 1. Compile-Only Tests (Type Checking)
8+
Files like `basic-types.test.ts`, `connection-methods.test.ts`, `type-constraints.test.ts`, `error-examples.test.ts`
9+
10+
- **Purpose**: Verify that TypeScript code compiles without errors
11+
- **Execution**: Not executed at runtime - only compiled
12+
- **Speed**: Very fast (seconds)
13+
- **Requirements**: No MarkLogic server needed
14+
- **Run with**: `npm run test:types`
15+
16+
These tests validate:
17+
- Type definitions are syntactically correct
18+
- Type constraints work (e.g., `authType` only accepts valid values)
19+
- IntelliSense will work for users
20+
- Type errors are caught at compile time
21+
22+
### 2. Runtime Tests
23+
Files like `checkConnection-runtime.test.ts`
624

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

31+
These tests validate:
32+
- Types compile correctly (compile-time check)
33+
- Real API calls return the expected types (runtime check)
34+
- TypeScript definitions accurately reflect the actual JavaScript behavior
35+
36+
## How to Test Types
37+
38+
### Type Checking Only
939
```bash
1040
npm run test:types
1141
```
1242

13-
This command runs `tsc --noEmit`, which checks for TypeScript errors without generating JavaScript files.
43+
This runs `tsc --noEmit`, which checks for TypeScript errors without generating JavaScript files.
44+
45+
### Runtime Tests
46+
```bash
47+
npm run test:compile # Compile TypeScript tests to JavaScript
48+
npx mocha test-typescript/*.js # Run compiled tests against MarkLogic
49+
```
50+
51+
Or in one command:
52+
```bash
53+
npm run test:compile && npx mocha test-typescript/*.js
54+
```
55+
56+
## Why Two Approaches?
1457

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

17-
TypeScript's compiler is the best way to test type definitions because:
18-
- It catches type errors at compile time (before runtime)
19-
- It validates type constraints (like union types for `authType`)
20-
- It ensures IntelliSense and autocomplete will work for users
21-
- It's fast and doesn't require running actual code
64+
**Runtime tests** are essential for:
65+
- Ensuring type definitions match actual behavior
66+
- Catching mismatches between declared types and runtime values
67+
- Integration testing with real MarkLogic instances
68+
- Preventing issues like returning `{}` when a `Promise` was expected
69+
70+
Both approaches complement each other for comprehensive type safety validation.
2271

2372
## Example: Testing for Type Errors
2473

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

4291
This confirms your types are working correctly!
92+
93+
## Adding New Tests
94+
95+
### To add a compile-only test:
96+
1. Create a `.test.ts` file in this directory
97+
2. Use `/// <reference path="../marklogic.d.ts" />` to load types
98+
3. Import types with: `type MyType = import('marklogic').MyType;`
99+
4. Write code that should compile (or intentionally fail)
100+
5. Run `npm run test:types` to verify
101+
102+
### To add a runtime test:
103+
1. Create a `.test.ts` file in this directory
104+
2. Use the same reference and import pattern as above
105+
3. Import test framework: `import should = require('should');`
106+
4. Use `describe`/`it` blocks like normal mocha tests
107+
5. Make actual API calls to MarkLogic
108+
6. Compile with `npm run test:compile` and run with mocha
109+
110+
**Note**: Compiled `.js` files are gitignored and regenerated on each test run.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2015-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3+
*/
4+
5+
/// <reference path="../marklogic.d.ts" />
6+
7+
/**
8+
* TypeScript runtime test to validate that checkConnection returns ResultProvider.
9+
* This test ensures the TypeScript definitions match the actual runtime behavior
10+
* by making real calls to MarkLogic AND verifying types at compile time.
11+
*/
12+
13+
import should = require('should');
14+
const marklogic = require('..');
15+
const testconfig = require('../etc/test-config-qa.js');
16+
17+
const db = marklogic.createDatabaseClient(testconfig.restReaderConnection);
18+
19+
// Type alias for easier reference
20+
type ResultProvider<T> = import('marklogic').ResultProvider<T>;
21+
type ConnectionCheckResult = import('marklogic').ConnectionCheckResult;
22+
type DatabaseClientConfig = import('marklogic').DatabaseClientConfig;
23+
24+
describe('checkConnection ResultProvider validation', function() {
25+
26+
it('should return ResultProvider with .result() method', function(done) {
27+
// This validates that checkConnection returns a ResultProvider
28+
// TypeScript will verify the type at compile time
29+
const resultProvider: ResultProvider<ConnectionCheckResult> = db.checkConnection();
30+
31+
// Verify it has a .result() method (core requirement for ResultProvider)
32+
should(resultProvider).have.property('result');
33+
should(resultProvider.result).be.a.Function();
34+
35+
// Call .result() to get a Promise
36+
const promise: Promise<ConnectionCheckResult> = resultProvider.result();
37+
38+
// Verify .result() returns a Promise (thenable)
39+
should(promise).have.property('then');
40+
should(promise.then).be.a.Function();
41+
42+
// Verify the Promise resolves to ConnectionCheckResult
43+
promise.then((response: ConnectionCheckResult) => {
44+
should(response).have.property('connected');
45+
should(response.connected).be.a.Boolean();
46+
47+
if (response.connected === true) {
48+
done();
49+
} else {
50+
done(new Error('Expected connection to succeed but got: ' + JSON.stringify(response)));
51+
}
52+
}).catch(done);
53+
});
54+
55+
it('should work with async/await pattern', async function() {
56+
// TypeScript verifies the return type matches ConnectionCheckResult
57+
const result: ConnectionCheckResult = await db.checkConnection().result();
58+
59+
// Verify result shape matches ConnectionCheckResult
60+
should(result).have.property('connected');
61+
should(result.connected).be.a.Boolean();
62+
should(result.connected).equal(true);
63+
});
64+
65+
it('should have error properties when connection fails', function(done) {
66+
// Test with wrong password to get a failed connection
67+
const config: DatabaseClientConfig = {
68+
host: testconfig.restReaderConnection.host,
69+
user: testconfig.restReaderConnection.user,
70+
password: 'wrongpassword', // Invalid password
71+
port: testconfig.restReaderConnection.port,
72+
authType: testconfig.restReaderConnection.authType
73+
};
74+
const db1 = marklogic.createDatabaseClient(config);
75+
76+
db1.checkConnection().result().then((response: ConnectionCheckResult) => {
77+
should(response).have.property('connected');
78+
should(response.connected).be.a.Boolean();
79+
80+
if (response.connected === false) {
81+
// When connected is false, optional error properties should exist
82+
should(response).have.property('httpStatusCode');
83+
should(response.httpStatusCode).be.a.Number();
84+
should(response).have.property('httpStatusMessage');
85+
should(response.httpStatusMessage).be.a.String();
86+
}
87+
88+
db1.release();
89+
done();
90+
}).catch(done);
91+
});
92+
93+
after(function(done) {
94+
db.release();
95+
done();
96+
});
97+
});

0 commit comments

Comments
 (0)