Skip to content

Commit

Permalink
feat!: core7 w/ jsforce-node (#359)
Browse files Browse the repository at this point in the history
* test: new top-level testSetup export

* feat!: core7, jsforce-node

BREAKING CHANGE: use new jsforce, core

* test: node16 module imports

* ci: beta release 5.0.0-beta.0

* Wr/timeout apex (#364)

* fix: timeout working, pjson locked, UT failing

* fix: use native clearInterval

* chore: undo pjson change

* test: add UT

* chore: temp beta

* chore: revert to 5.0.0

* fix: allow test to finsih first and not hang

* chore: temp beta

* test: add assertions for test return before timeout

---------

Co-authored-by: Willie Ruemmele <[email protected]>
Co-authored-by: Willhoit <[email protected]>
Co-authored-by: peternhale <[email protected]>
  • Loading branch information
4 people authored Apr 23, 2024
1 parent 95fb994 commit e52a4c4
Show file tree
Hide file tree
Showing 23 changed files with 199 additions and 210 deletions.
24 changes: 19 additions & 5 deletions .github/workflows/onRelease.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
# when a github release happens, publish an npm package,
name: publish

on:
release:
types: [released]
# support manual release
types: [published]
# support manual release in case something goes wrong and needs to be repeated or tested
workflow_dispatch:
inputs:
tag:
description: tag that needs to publish
type: string
required: true
jobs:
# parses the package.json version and detects prerelease tag (ex: beta from 4.4.4-beta.0)
getDistTag:
outputs:
tag: ${{ steps.distTag.outputs.tag }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
- uses: salesforcecli/github-workflows/.github/actions/getPreReleaseTag@main
id: distTag

npm:
uses: salesforcecli/github-workflows/.github/workflows/npmPublish.yml@main
needs: [getDistTag]
with:
ctc: false
tag: ${{ needs.getDistTag.outputs.tag || 'latest' }}
githubTag: ${{ github.event.release.tag_name || inputs.tag }}
secrets: inherit
secrets: inherit
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"name": "@salesforce/apex-node",
"description": "Salesforce JS library for Apex",
"version": "4.0.6",
"version": "5.0.0",
"author": "Salesforce",
"bugs": "https://github.com/forcedotcom/salesforcedx-apex/issues",
"main": "lib/src/index.js",
"dependencies": {
"@salesforce/core": "^6.7.4",
"@jsforce/jsforce-node": "^3.1.0",
"@salesforce/core": "^7.2.0",
"@salesforce/kit": "^3.1.0",
"@types/istanbul-reports": "^3.0.4",
"faye": "1.4.0",
"glob": "^10.3.10",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.1.6",
"jsforce": "^2.0.0-beta.29"
"istanbul-reports": "^3.1.6"
},
"devDependencies": {
"@commitlint/config-conventional": "^18.1.0",
Expand Down Expand Up @@ -89,4 +89,4 @@
"publishConfig": {
"access": "public"
}
}
}
16 changes: 7 additions & 9 deletions src/execute/executeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
import { Connection } from '@salesforce/core';
import { existsSync, readFileSync } from 'fs';
import {
ExecuteAnonymousResponse,
action,
ApexExecuteOptions,
SoapResponse,
soapEnv,
ExecuteAnonymousResponse,
soapBody,
soapEnv,
soapHeader,
action
SoapResponse
} from './types';
import { nls } from '../i18n';
import { refreshAuth } from '../utils';
import { encodeBody } from './utils';
import * as readline from 'readline';
import { HttpRequest } from 'jsforce';
import type { HttpRequest } from '@jsforce/jsforce-node';
import { elapsedTime } from '../utils/elapsedTime';

export class ExecuteService {
Expand Down Expand Up @@ -121,14 +121,12 @@ export class ExecuteService {
'content-type': 'text/xml',
soapaction: action
};
const request: HttpRequest = {
return {
method: 'POST',
url: postEndpoint,
body,
headers: requestHeaders
};

return request;
}

@elapsedTime()
Expand Down Expand Up @@ -170,6 +168,6 @@ export class ExecuteService {
public async connectionRequest(
requestData: HttpRequest
): Promise<SoapResponse> {
return (await this.connection.request(requestData)) as SoapResponse;
return this.connection.request(requestData);
}
}
17 changes: 5 additions & 12 deletions src/logs/logService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,12 @@ import {
import { Duration } from '@salesforce/kit';
import { AnyJson } from '@salesforce/ts-types';
import {
LOG_TIMER_LENGTH_MINUTES,
LISTENER_ABORTED_ERROR_NAME,
LOG_TIMER_LENGTH_MINUTES,
MAX_NUM_LOGS,
STREAMING_LOG_TOPIC
} from './constants';
import {
ApexLogGetOptions,
LogQueryResult,
LogRecord,
LogResult
} from './types';
import { ApexLogGetOptions, LogRecord, LogResult } from './types';
import * as path from 'path';
import { nls } from '../i18n';
import { createFile } from '../utils';
Expand Down Expand Up @@ -97,7 +92,7 @@ export class LogService {
public async getLogById(logId: string): Promise<LogResult> {
const baseUrl = this.connection.tooling._baseUrl();
const url = `${baseUrl}/sobjects/ApexLog/${logId}/Body`;
const response = (await this.connection.tooling.request(url)) as AnyJson;
const response = await this.connection.tooling.request<AnyJson>(url);
return { log: response.toString() || '' };
}

Expand All @@ -118,10 +113,8 @@ export class LogService {
apexLogQuery += ` LIMIT ${numberOfLogs}`;
}

const response = (await this.connection.tooling.query(
apexLogQuery
)) as LogQueryResult;
return response.records as LogRecord[];
return (await this.connection.tooling.query<LogRecord>(apexLogQuery))
.records;
}

@elapsedTime()
Expand Down
31 changes: 24 additions & 7 deletions src/streaming/streamingClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import {
ApexTestProgressValue,
ApexTestQueueItem,
ApexTestQueueItemRecord,
ApexTestQueueItemStatus
ApexTestQueueItemStatus,
TestRunIdResult
} from '../tests/types';
import { Duration } from '@salesforce/kit';

const TEST_RESULT_CHANNEL = '/systemTopic/TestResult';
const DEFAULT_STREAMING_TIMEOUT_MS = 14400;
export const DEFAULT_STREAMING_TIMEOUT_SEC = 14400;

export interface AsyncTestRun {
runId: string;
Expand All @@ -44,9 +46,8 @@ export class StreamingClient {
// that is exported from jsforce.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private client: any;
private conn: Connection;
private readonly conn: Connection;
private progress?: Progress<ApexTestProgressValue>;
private apiVersion = '36.0';
public subscribedTestRunId: string;
private subscribedTestRunIdDeferred = new Deferred<string>();
public get subscribedTestRunIdPromise(): Promise<string> {
Expand Down Expand Up @@ -74,7 +75,7 @@ export class StreamingClient {
this.progress = progress;
const streamUrl = this.getStreamURL(this.conn.instanceUrl);
this.client = new Client(streamUrl, {
timeout: DEFAULT_STREAMING_TIMEOUT_MS
timeout: DEFAULT_STREAMING_TIMEOUT_SEC
});

this.client.on('transport:up', () => {
Expand Down Expand Up @@ -166,10 +167,21 @@ export class StreamingClient {
@elapsedTime()
public async subscribe(
action?: () => Promise<string>,
testRunId?: string
): Promise<AsyncTestRun> {
testRunId?: string,
timeout?: Duration
): Promise<AsyncTestRun | TestRunIdResult> {
return new Promise((subscriptionResolve, subscriptionReject) => {
let intervalId: NodeJS.Timeout;
// start timeout
const timeoutId = setTimeout(
() => {
this.disconnect();
clearInterval(intervalId);
subscriptionResolve({ testRunId });
},
timeout?.milliseconds ?? DEFAULT_STREAMING_TIMEOUT_SEC * 1000
);

try {
this.client.subscribe(
TEST_RESULT_CHANNEL,
Expand All @@ -179,6 +191,7 @@ export class StreamingClient {
if (result) {
this.disconnect();
clearInterval(intervalId);
clearTimeout(timeoutId);
subscriptionResolve({
runId: this.subscribedTestRunId,
queueItem: result
Expand All @@ -199,6 +212,7 @@ export class StreamingClient {
if (result) {
this.disconnect();
clearInterval(intervalId);
clearTimeout(timeoutId);
subscriptionResolve({
runId: this.subscribedTestRunId,
queueItem: result
Expand All @@ -210,6 +224,7 @@ export class StreamingClient {
.catch((e) => {
this.disconnect();
clearInterval(intervalId);
clearTimeout(timeoutId);
subscriptionReject(e);
});
} else {
Expand All @@ -222,6 +237,7 @@ export class StreamingClient {
if (result) {
this.disconnect();
clearInterval(intervalId);
clearTimeout(timeoutId);
subscriptionResolve({
runId: this.subscribedTestRunId,
queueItem: result
Expand All @@ -232,6 +248,7 @@ export class StreamingClient {
}
} catch (e) {
this.disconnect();
clearTimeout(timeoutId);
clearInterval(intervalId);
subscriptionReject(e);
}
Expand Down
36 changes: 23 additions & 13 deletions src/tests/asyncTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { calculatePercentage, queryAll } from './utils';
import * as util from 'util';
import { QUERY_RECORD_LIMIT } from './constants';
import { CodeCoverage } from './codeCoverage';
import { HttpRequest } from 'jsforce';
import { isValidTestRunID } from '../narrowing';
import { Duration } from '@salesforce/kit';

const finishedStatuses = [
ApexTestRunResultStatus.Aborted,
Expand All @@ -57,14 +57,16 @@ export class AsyncTests {
* @param exitOnTestRunId should not wait for test run to complete, return test run id immediately
* @param progress progress reporter
* @param token cancellation token
* @param timeout Duration to wait before returning a TestRunIdResult
*/
@elapsedTime()
public async runTests(
options: AsyncTestConfiguration | AsyncTestArrayConfiguration,
codeCoverage = false,
exitOnTestRunId = false,
progress?: Progress<ApexTestProgressValue>,
token?: CancellationToken
token?: CancellationToken,
timeout?: Duration
): Promise<TestResult | TestRunIdResult> {
try {
const sClient = new StreamingClient(this.connection, progress);
Expand All @@ -87,8 +89,17 @@ export class AsyncTests {
if (token && token.isCancellationRequested) {
return null;
}
const asyncRunResult = await sClient.subscribe(
undefined,
testRunId,
timeout
);

if ('testRunId' in asyncRunResult) {
// timeout, return the id
return { testRunId };
}

const asyncRunResult = await sClient.subscribe(undefined, testRunId);
const runResult = await this.checkRunStatus(asyncRunResult.runId);
return await this.formatAsyncResults(
asyncRunResult,
Expand Down Expand Up @@ -124,7 +135,9 @@ export class AsyncTests {
if (runResult.testsComplete) {
queueItem = await sClient.handler(undefined, testRunId);
} else {
queueItem = (await sClient.subscribe(undefined, testRunId)).queueItem;
queueItem = (
(await sClient.subscribe(undefined, testRunId)) as AsyncTestRun
).queueItem;
runResult = await this.checkRunStatus(testRunId);
}

Expand Down Expand Up @@ -426,17 +439,14 @@ export class AsyncTests {
): () => Promise<string> {
const requestTestRun = async (): Promise<string> => {
const url = `${this.connection.tooling._baseUrl()}/runTestsAsynchronous`;
const request: HttpRequest = {
method: 'POST',
url,
body: JSON.stringify(options),
headers: { 'content-type': 'application/json' }
};

try {
const testRunId = (await this.connection.tooling.request(
request
)) as string;
const testRunId = await this.connection.tooling.request<string>({
method: 'POST',
url,
body: JSON.stringify(options),
headers: { 'content-type': 'application/json' }
});
return Promise.resolve(testRunId);
} catch (e) {
return Promise.reject(e);
Expand Down
2 changes: 1 addition & 1 deletion src/tests/syncTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
TestResult
} from './types';
import { calculatePercentage } from './utils';
import { HttpRequest } from 'jsforce';
import type { HttpRequest } from '@jsforce/jsforce-node';
import { elapsedTime } from '../utils/elapsedTime';

export class SyncTests {
Expand Down
7 changes: 5 additions & 2 deletions src/tests/testService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { JSONStringifyStream, TestResultStringifyStream } from '../streaming';
import { CodeCoverageStringifyStream } from '../streaming/codeCoverageStringifyStream';
import { elapsedTime } from '../utils';
import { isTestResult, isValidApexClassID } from '../narrowing';
import { Duration } from '@salesforce/kit';

export class TestService {
private readonly connection: Connection;
Expand Down Expand Up @@ -209,14 +210,16 @@ export class TestService {
codeCoverage = false,
immediatelyReturn = false,
progress?: Progress<ApexTestProgressValue>,
token?: CancellationToken
token?: CancellationToken,
timeout?: Duration
): Promise<TestResult | TestRunIdResult> {
return await this.asyncService.runTests(
options,
codeCoverage,
immediatelyReturn,
progress,
token
token,
timeout
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { Connection } from '@salesforce/core';
import { NamespaceInfo } from './types';
import { QueryResult } from 'jsforce';
import type { QueryResult } from '@jsforce/jsforce-node';

export function calculatePercentage(dividend: number, divisor: number): string {
let percentage = '0%';
Expand Down
2 changes: 1 addition & 1 deletion src/utils/authUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Connection } from '@salesforce/core';
import { JsonCollection } from '@salesforce/ts-types';
import { xmlCharMap } from './types';
import { HttpRequest } from 'jsforce';
import type { HttpRequest } from '@jsforce/jsforce-node';

export async function refreshAuth(
connection: Connection
Expand Down
Loading

0 comments on commit e52a4c4

Please sign in to comment.