Skip to content

Commit

Permalink
Wr/timeout apex (#364)
Browse files Browse the repository at this point in the history
* fix: timeout working, pjson locked, UT failing

* fix: use native clearInterval

* chore: undo pjson change

* test: add UT
  • Loading branch information
WillieRuemmele authored Apr 16, 2024
1 parent 4fd302f commit e9d51ba
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 49 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"main": "lib/src/index.js",
"dependencies": {
"@jsforce/jsforce-node": "^3.1.0",
"@salesforce/core": "^7.0.0",
"@salesforce/core": "^7.2.0",
"@salesforce/kit": "^3.1.0",
"@types/istanbul-reports": "^3.0.4",
"faye": "1.4.0",
Expand Down Expand Up @@ -89,4 +89,4 @@
"publishConfig": {
"access": "public"
}
}
}
14 changes: 6 additions & 8 deletions src/execute/executeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
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';
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
26 changes: 19 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
setTimeout(
() => {
this.disconnect();
clearInterval(intervalId);
subscriptionResolve({ testRunId });
},
timeout?.milliseconds ?? DEFAULT_STREAMING_TIMEOUT_SEC * 1000
);

try {
this.client.subscribe(
TEST_RESULT_CHANNEL,
Expand Down
35 changes: 22 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 type { HttpRequest } from '@jsforce/jsforce-node';
import { isValidTestRunID } from '../narrowing';
import { Duration } from '@salesforce/kit';

const finishedStatuses = [
ApexTestRunResultStatus.Aborted,
Expand Down Expand Up @@ -64,7 +64,8 @@ export class AsyncTests {
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 +88,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 +134,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 +438,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
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
27 changes: 27 additions & 0 deletions test/streaming/streamingClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '../../src/tests/types';
import { nls } from '../../src/i18n';
import { EventEmitter } from 'events';
import { Duration } from '@salesforce/kit';

// The type defined in jsforce doesn't have all Faye client methods.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -509,6 +510,32 @@ describe('Streaming API Client', () => {
assert.calledOnce(mockToolingQuery);
});

it('should return the run id, disconnect and clear interval on timeout', async () => {
sandboxStub.stub(ApexFayeClient.prototype, 'subscribe');
const disconnectStub = sandboxStub.stub(
ApexFayeClient.prototype,
'disconnect'
);
const mockRunId = '707xx0000AGQ3jbQQD';

const setIntervalStub = sandboxStub.stub(global, 'setInterval');
setIntervalStub.callsFake((callback: Function) => callback.call(null));
const clearIntervalStub = sandboxStub.stub(global, 'clearInterval');

const streamClient = new StreamingClient(mockConnection);
const result = await streamClient.subscribe(
() => Promise.resolve(mockRunId),
mockRunId,
Duration.milliseconds(10)
);

expect(result).to.eql({
testRunId: mockRunId
});
assert.calledOnce(disconnectStub);
assert.calledOnce(clearIntervalStub);
});

it('should return the results, disconnect and clear interval on test completion', async () => {
sandboxStub.stub(ApexFayeClient.prototype, 'subscribe');
const disconnectStub = sandboxStub.stub(
Expand Down
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -546,10 +546,10 @@
picocolors "^1.0.0"
tslib "^2.6.0"

"@salesforce/core@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.0.0.tgz#f680cff11192cdf939067959908613365bf4bf2e"
integrity sha512-yoZFx1jC75D4uGLGRNKIzxS2W+AHExFD4wT9whLox1c2B48nxZjaEB2zB3d9dBtoZcVD5KI5AvmPGJ7eyPnLHA==
"@salesforce/core@^7.2.0":
version "7.3.0"
resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-7.3.0.tgz#2ad3dfccb1ef0eb2e65b49b655e39748002bbc13"
integrity sha512-c/gZLvKFHvgAv/Gyd4LjGGQykvGLn67QtCmdT7Hnm57bTDZoyr7XJXcaI+ILN0NO47guG1tEWP5eBvAi+u2DNA==
dependencies:
"@jsforce/jsforce-node" "^3.1.0"
"@salesforce/kit" "^3.1.0"
Expand Down Expand Up @@ -748,7 +748,14 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.6.tgz#818551d39113081048bdddbef96701b4e8bb9d1b"
integrity sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==

"@types/node@*", "@types/node@^18", "@types/node@^18.11.9", "@types/node@^18.15.3":
"@types/node@*", "@types/node@^18", "@types/node@^18.11.9":
version "18.19.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.3.tgz#e4723c4cb385641d61b983f6fe0b716abd5f8fc0"
integrity sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==
dependencies:
undici-types "~5.26.4"

"@types/node@^18.15.3":
version "18.19.31"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.31.tgz#b7d4a00f7cb826b60a543cebdbda5d189aaecdcd"
integrity sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==
Expand Down

0 comments on commit e9d51ba

Please sign in to comment.