From f78cfc7627f7d1dc7fde73b94d9911410f16dcfc Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Thu, 21 Mar 2024 15:29:21 -0600 Subject: [PATCH 1/6] fix: guard against undefined test results @W-15298950@ Account for a undefined ApexTestRunResult when formatting results --- src/execute/executeService.ts | 1 + src/i18n/i18n.ts | 3 +- src/i18n/index.ts | 4 +- src/streaming/streamingClient.ts | 20 +- src/tests/asyncTests.ts | 77 ++++--- src/tests/testService.ts | 10 +- src/tests/types.ts | 8 +- test/tests/asyncTests.test.ts | 357 ++++++++++++++----------------- tsconfig.json | 3 +- 9 files changed, 217 insertions(+), 266 deletions(-) diff --git a/src/execute/executeService.ts b/src/execute/executeService.ts index c838dbe2..ef5896f0 100644 --- a/src/execute/executeService.ts +++ b/src/execute/executeService.ts @@ -56,6 +56,7 @@ export class ExecuteService { } } } + throw new Error(nls.localize('authForAnonymousApexFailed')); } @elapsedTime() diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 0905d220..33968296 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -91,5 +91,6 @@ export const messages = { covIdFormatErr: 'Cannot specify code coverage with a TestRunId result', startHandshake: 'Attempting StreamingClient handshake', finishHandshake: 'Finished StreamingClient handshake', - subscribeStarted: 'Subscribing to ApexLog events' + subscribeStarted: 'Subscribing to ApexLog events', + authForAnonymousApexFailed: 'The authentication for execute anonymous failed' }; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 77046ce6..2ebbc435 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -10,11 +10,11 @@ import { Localization, Message } from './localization'; function loadMessageBundle(): Message { try { - const layer = new Message(messages); - return layer; + return new Message(messages); } catch (e) { console.error('Cannot find messages in i18n module'); } + return undefined; } export const nls = new Localization(loadMessageBundle()); diff --git a/src/streaming/streamingClient.ts b/src/streaming/streamingClient.ts index bf6ebf98..f90fcf5a 100644 --- a/src/streaming/streamingClient.ts +++ b/src/streaming/streamingClient.ts @@ -296,16 +296,16 @@ export class StreamingClient { value: result }); - for (let i = 0; i < result.records.length; i++) { - const item = result.records[i]; - if ( - item.Status === ApexTestQueueItemStatus.Queued || - item.Status === ApexTestQueueItemStatus.Holding || - item.Status === ApexTestQueueItemStatus.Preparing || - item.Status === ApexTestQueueItemStatus.Processing - ) { - return null; - } + if ( + result.records.some( + (item) => + item.Status === ApexTestQueueItemStatus.Queued || + item.Status === ApexTestQueueItemStatus.Holding || + item.Status === ApexTestQueueItemStatus.Preparing || + item.Status === ApexTestQueueItemStatus.Processing + ) + ) { + return null; } return result; } diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index 59aaacf0..0d557734 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -5,11 +5,13 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +'use strict'; + import { Connection } from '@salesforce/core'; import { CancellationToken, Progress } from '../common'; import { nls } from '../i18n'; import { AsyncTestRun, StreamingClient } from '../streaming'; -import { formatStartTime, getCurrentTime } from '../utils'; +import { elapsedTime, formatStartTime, getCurrentTime } from '../utils'; import { formatTestErrors, getAsyncDiagnostic } from './diagnosticUtil'; import { ApexTestProgressValue, @@ -20,7 +22,6 @@ import { ApexTestResultData, ApexTestResultOutcome, ApexTestRunResult, - ApexTestRunResultRecord, ApexTestRunResultStatus, AsyncTestArrayConfiguration, AsyncTestConfiguration, @@ -32,7 +33,14 @@ import * as util from 'util'; import { QUERY_RECORD_LIMIT } from './constants'; import { CodeCoverage } from './codeCoverage'; import { HttpRequest } from 'jsforce'; -import { elapsedTime } from '../utils/elapsedTime'; + +const finishedStatuses = [ + ApexTestRunResultStatus.Aborted, + ApexTestRunResultStatus.Failed, + ApexTestRunResultStatus.Completed, + ApexTestRunResultStatus.Passed, + ApexTestRunResultStatus.Skipped +]; export class AsyncTests { public readonly connection: Connection; @@ -145,16 +153,12 @@ export class AsyncTests { public async checkRunStatus( testRunId: string, progress?: Progress - ): Promise { + ): Promise { if (!isValidTestRunID(testRunId)) { throw new Error(nls.localize('invalidTestRunIdErr', testRunId)); } - let testRunSummaryQuery = - 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; - testRunSummaryQuery += - 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; - testRunSummaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; + const testRunSummaryQuery = `SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, MethodsEnqueued, StartTime, EndTime, TestTime, UserId FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; progress?.report({ type: 'FormatTestResultProgress', @@ -162,32 +166,21 @@ export class AsyncTests { message: nls.localize('retrievingTestRunSummary') }); - const testRunSummaryResults = (await this.connection.tooling.query( - testRunSummaryQuery, - { - autoFetch: true - } - )) as ApexTestRunResult; + try { + const testRunSummaryResults = (await this.connection.singleRecordQuery( + testRunSummaryQuery, + { + tooling: true + } + )) as ApexTestRunResult; - if (testRunSummaryResults.records.length === 0) { + if (finishedStatuses.includes(testRunSummaryResults.Status)) { + return testRunSummaryResults; + } + } catch (e) { throw new Error(nls.localize('noTestResultSummary', testRunId)); } - if ( - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Aborted || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Failed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Completed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Passed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Skipped - ) { - return testRunSummaryResults.records[0]; - } - return undefined; } @@ -196,7 +189,7 @@ export class AsyncTests { * @param asyncRunResult TestQueueItem and RunId for an async run * @param commandStartTime start time for the async test run * @param codeCoverage should report code coverages - * @param testRunSummary test run summary + * @param testRunSummary test run summary | undefined * @param progress progress reporter * @returns */ @@ -205,7 +198,7 @@ export class AsyncTests { asyncRunResult: AsyncTestRun, commandStartTime: number, codeCoverage = false, - testRunSummary: ApexTestRunResultRecord, + testRunSummary: ApexTestRunResult | undefined, progress?: Progress ): Promise { const coveredApexClassIdSet = new Set(); @@ -215,12 +208,12 @@ export class AsyncTests { const { apexTestClassIdSet, testResults, globalTests } = await this.buildAsyncTestResults(apexTestResults); - let outcome = testRunSummary.Status; + let outcome = testRunSummary?.Status ?? 'Unknown'; if (globalTests.failed > 0) { outcome = ApexTestRunResultStatus.Failed; } else if (globalTests.passed === 0) { outcome = ApexTestRunResultStatus.Skipped; - } else if (testRunSummary.Status === ApexTestRunResultStatus.Completed) { + } else if (testRunSummary?.Status === ApexTestRunResultStatus.Completed) { outcome = ApexTestRunResultStatus.Passed; } @@ -235,15 +228,17 @@ export class AsyncTests { passRate: calculatePercentage(globalTests.passed, testResults.length), failRate: calculatePercentage(globalTests.failed, testResults.length), skipRate: calculatePercentage(globalTests.skipped, testResults.length), - testStartTime: formatStartTime(testRunSummary.StartTime, 'ISO'), - testExecutionTimeInMs: testRunSummary.TestTime ?? 0, - testTotalTimeInMs: testRunSummary.TestTime ?? 0, + testStartTime: testRunSummary?.StartTime + ? formatStartTime(testRunSummary.StartTime, 'ISO') + : '', + testExecutionTimeInMs: testRunSummary?.TestTime ?? 0, + testTotalTimeInMs: testRunSummary?.TestTime ?? 0, commandTimeInMs: getCurrentTime() - commandStartTime, hostname: this.connection.instanceUrl, orgId: this.connection.getAuthInfoFields().orgId, username: this.connection.getUsername(), testRunId: asyncRunResult.runId, - userId: testRunSummary.UserId + userId: testRunSummary?.UserId ?? 'Unknown' }, tests: testResults }; @@ -394,6 +389,7 @@ export class AsyncTests { /** * Abort test run with test run id * @param testRunId + * @param progress */ public async abortTestRun( testRunId: string, @@ -430,7 +426,7 @@ export class AsyncTests { private getTestRunRequestAction( options: AsyncTestConfiguration | AsyncTestArrayConfiguration ): () => Promise { - const requestTestRun = async (): Promise => { + return async (): Promise => { const url = `${this.connection.tooling._baseUrl()}/runTestsAsynchronous`; const request: HttpRequest = { method: 'POST', @@ -448,6 +444,5 @@ export class AsyncTests { return Promise.reject(e); } }; - return requestTestRun; } } diff --git a/src/tests/testService.ts b/src/tests/testService.ts index 2f6e5d63..374aebd3 100644 --- a/src/tests/testService.ts +++ b/src/tests/testService.ts @@ -29,7 +29,7 @@ import { AsyncTests } from './asyncTests'; import { SyncTests } from './syncTests'; import { formatTestErrors } from './diagnosticUtil'; import { QueryResult } from '../utils/types'; -import { elapsedTime } from '../utils/elapsedTime'; +import { elapsedTime } from '../utils'; export class TestService { private readonly connection: Connection; @@ -341,11 +341,9 @@ export class TestService { try { if (tests) { const payload = await this.buildTestPayload(tests); - const classes = payload.tests?.map((testItem) => { - if (testItem.className) { - return testItem.className; - } - }); + const classes = payload.tests + ?.filter((testItem) => testItem.className) + .map((testItem) => testItem.className); if (new Set(classes).size !== 1) { throw new Error(nls.localize('syncClassErr')); } diff --git a/src/tests/types.ts b/src/tests/types.ts index 95b5de8f..0ca0de6f 100644 --- a/src/tests/types.ts +++ b/src/tests/types.ts @@ -255,7 +255,7 @@ export const enum ApexTestRunResultStatus { Skipped = 'Skipped' } -export type ApexTestRunResultRecord = { +export type ApexTestRunResult = { /** * The parent Apex job ID for the result */ @@ -278,12 +278,6 @@ export type ApexTestRunResultRecord = { UserId: string; }; -export type ApexTestRunResult = { - done: boolean; - totalSize: number; - records: ApexTestRunResultRecord[]; -}; - export const enum ApexTestQueueItemStatus { Holding = 'Holding', Queued = 'Queued', diff --git a/test/tests/asyncTests.test.ts b/test/tests/asyncTests.test.ts index d20415b9..752f4a0e 100644 --- a/test/tests/asyncTests.test.ts +++ b/test/tests/asyncTests.test.ts @@ -16,7 +16,15 @@ import { SinonSpy, SinonStub } from 'sinon'; -import { TestService, OutputDirConfig } from '../../src/tests'; +import { + TestService, + OutputDirConfig, + CancellationTokenSource, + JUnitReporter, + TapReporter, + Progress, + ApexTestProgressValue +} from '../../src'; import { AsyncTestConfiguration, TestLevel, @@ -24,7 +32,6 @@ import { ApexTestResultOutcome, ApexTestQueueItem, ApexTestRunResultStatus, - ApexTestRunResult, ApexTestResult, ApexOrgWideCoverage, ApexCodeCoverageAggregate, @@ -52,13 +59,6 @@ import { join } from 'path'; import * as stream from 'stream'; import * as fs from 'fs'; import * as diagnosticUtil from '../../src/tests/diagnosticUtil'; -import { - CancellationTokenSource, - JUnitReporter, - TapReporter, - Progress, - ApexTestProgressValue -} from '../../src'; import * as utils from '../../src/tests/utils'; import { AsyncTests } from '../../src/tests/asyncTests'; import { QUERY_RECORD_LIMIT } from '../../src/tests/constants'; @@ -170,22 +170,20 @@ describe('Run Apex tests asynchronously', () => { mockConnection.getAuthInfoFields().orgId; missingTimeTestData.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -221,7 +219,7 @@ describe('Run Apex tests asynchronously', () => { 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; summaryQuery += 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; summaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; - expect(mockToolingQuery.getCall(0).args[0]).to.equal(summaryQuery); + expect(mockSingleRecordQuery.getCall(0).args[0]).to.equal(summaryQuery); let testResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, '; testResultQuery += @@ -229,27 +227,25 @@ describe('Run Apex tests asynchronously', () => { testResultQuery += 'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix '; testResultQuery += `FROM ApexTestResult WHERE QueueItemId IN ('${pollResponse.records[0].Id}')`; - expect(mockToolingQuery.getCall(1).args[0]).to.equal(testResultQuery); + expect(mockToolingQuery.getCall(0).args[0]).to.equal(testResultQuery); expect(getTestResultData).to.deep.equals(missingTimeTestData); }); it('should report progress when checking test summary for run', async () => { const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); - mockToolingQuery.onSecondCall().resolves({ done: true, totalSize: 1, records: [ @@ -292,22 +288,20 @@ describe('Run Apex tests asynchronously', () => { skippedTestData.summary.orgId = mockConnection.getAuthInfoFields().orgId; skippedTestData.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -344,7 +338,7 @@ describe('Run Apex tests asynchronously', () => { 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; summaryQuery += 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; summaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; - expect(mockToolingQuery.getCall(0).args[0]).to.equal(summaryQuery); + expect(mockSingleRecordQuery.getCall(0).args[0]).to.equal(summaryQuery); let testResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, '; testResultQuery += @@ -352,7 +346,7 @@ describe('Run Apex tests asynchronously', () => { testResultQuery += 'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix '; testResultQuery += `FROM ApexTestResult WHERE QueueItemId IN ('${pollResponse.records[0].Id}')`; - expect(mockToolingQuery.getCall(1).args[0]).to.equal(testResultQuery); + expect(mockToolingQuery.getCall(0).args[0]).to.equal(testResultQuery); expect(getTestResultData).to.deep.equals(skippedTestData); }); @@ -360,22 +354,20 @@ describe('Run Apex tests asynchronously', () => { diagnosticResult.summary.orgId = mockConnection.getAuthInfoFields().orgId; diagnosticResult.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -418,22 +410,20 @@ describe('Run Apex tests asynchronously', () => { diagnosticFailure.tests[0].diagnostic.exceptionStackTrace = undefined; diagnosticFailure.tests[0].stackTrace = undefined; const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -471,12 +461,11 @@ describe('Run Apex tests asynchronously', () => { it('should return an error if no test results are found', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().throwsException('No records found'); try { const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); @@ -497,12 +486,11 @@ describe('Run Apex tests asynchronously', () => { it('should return an error if invalid test run id was provided', async () => { const invalidId = '000000xxxxx'; const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves(undefined); try { await asyncTestSrv.checkRunStatus(invalidId); @@ -511,19 +499,18 @@ describe('Run Apex tests asynchronously', () => { expect(e.message).to.equal( nls.localize('invalidTestRunIdErr', invalidId) ); - expect(mockToolingQuery.notCalled).to.be.true; + expect(mockSingleRecordQuery.notCalled).to.be.true; } }); it('should return an error if invalid test run id prefix was provided', async () => { const invalidId = '708000000xxxxxx'; const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves(undefined); try { await asyncTestSrv.checkRunStatus(invalidId); @@ -532,7 +519,7 @@ describe('Run Apex tests asynchronously', () => { expect(e.message).to.equal( nls.localize('invalidTestRunIdErr', invalidId) ); - expect(mockToolingQuery.notCalled).to.be.true; + expect(mockSingleRecordQuery.notCalled).to.be.true; } }); @@ -542,40 +529,37 @@ describe('Run Apex tests asynchronously', () => { mockConnection.tooling, 'query' ); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: '2020-07-12T02:54:47.000+0000', + TestTime: 1765, + UserId: '005xx000000abcDAAU' + }); mockToolingAutoQuery.onCall(0).resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: '2020-07-12T02:54:47.000+0000', - TestTime: 1765, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); - - mockToolingAutoQuery.onCall(1).resolves({ done: true, totalSize: 6, records: mixedTestResults } as ApexTestResult); - mockToolingAutoQuery.onCall(2).resolves({ + mockToolingAutoQuery.onCall(1).resolves({ done: true, totalSize: 3, records: mixedPerClassCodeCoverage } as ApexCodeCoverage); - mockToolingAutoQuery.onCall(3).resolves({ + mockToolingAutoQuery.onCall(2).resolves({ done: true, totalSize: 3, records: codeCoverageQueryResult } as ApexCodeCoverageAggregate); - mockToolingAutoQuery.onCall(4).resolves({ + mockToolingAutoQuery.onCall(3).resolves({ done: true, totalSize: 1, records: [ @@ -613,40 +597,38 @@ describe('Run Apex tests asynchronously', () => { it('should report progress for aggregating code coverage', async () => { const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onCall(0).resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: '2020-07-12T02:54:47.000+0000', - TestTime: 1765, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onCall(0).resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: '2020-07-12T02:54:47.000+0000', + TestTime: 1765, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onCall(1).resolves({ + mockToolingQuery.onCall(0).resolves({ done: true, totalSize: 6, records: mixedTestResults } as ApexTestResult); - mockToolingQuery.onCall(2).resolves({ + mockToolingQuery.onCall(1).resolves({ done: true, totalSize: 3, records: mixedPerClassCodeCoverage } as ApexCodeCoverage); - mockToolingQuery.onCall(3).resolves({ + mockToolingQuery.onCall(2).resolves({ done: true, totalSize: 3, records: codeCoverageQueryResult } as ApexCodeCoverageAggregate); - mockToolingQuery.onCall(4).resolves({ + mockToolingQuery.onCall(3).resolves({ done: true, totalSize: 1, records: [ @@ -1230,39 +1212,28 @@ describe('Run Apex tests asynchronously', () => { describe('Report Test Run Status', async () => { it('should subscribe to test run for run still in progress', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery .onFirstCall() .resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Queued, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult) + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Queued, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }) .onSecondCall() .resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults' @@ -1293,7 +1264,7 @@ describe('Run Apex tests asynchronously', () => { await asyncTestSrv.reportAsyncResults(testRunId); - expect(mockToolingQuery.calledTwice).to.be.true; + expect(mockSingleRecordQuery.calledTwice).to.be.true; expect(formatResultsStub.calledOnce).to.be.true; expect(subscribeStub.calledOnce).to.be.true; expect(handlerStub.notCalled).to.be.true; @@ -1301,23 +1272,18 @@ describe('Run Apex tests asynchronously', () => { it('should query for test run results if run is complete', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults' @@ -1348,7 +1314,7 @@ describe('Run Apex tests asynchronously', () => { await asyncTestSrv.reportAsyncResults(testRunId); - expect(mockToolingQuery.calledOnce).to.be.true; + expect(mockSingleRecordQuery.calledOnce).to.be.true; expect(formatResultsStub.calledOnce).to.be.true; expect(subscribeStub.notCalled).to.be.true; expect(handlerStub.calledOnce).to.be.true; @@ -1356,23 +1322,18 @@ describe('Run Apex tests asynchronously', () => { it('should format results with retrieved test run summary', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults' diff --git a/tsconfig.json b/tsconfig.json index 384d6d22..78c1f9c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "outDir": "lib", "preserveConstEnums": true, "skipLibCheck": true, - "experimentalDecorators": true + "experimentalDecorators": true, + "noImplicitReturns": true }, "exclude": ["node_modules", "lib"] } From a46dda48dd9e8d05f64d88db3637c78fc5e163a0 Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Fri, 22 Mar 2024 14:52:55 -0600 Subject: [PATCH 2/6] chore: apply review suggestions and write test --- src/tests/asyncTests.ts | 2 +- test/tests/asyncTests.test.ts | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index 0d557734..1493f0cb 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -213,7 +213,7 @@ export class AsyncTests { outcome = ApexTestRunResultStatus.Failed; } else if (globalTests.passed === 0) { outcome = ApexTestRunResultStatus.Skipped; - } else if (testRunSummary?.Status === ApexTestRunResultStatus.Completed) { + } else if (outcome === ApexTestRunResultStatus.Completed) { outcome = ApexTestRunResultStatus.Passed; } diff --git a/test/tests/asyncTests.test.ts b/test/tests/asyncTests.test.ts index 752f4a0e..f5749fbc 100644 --- a/test/tests/asyncTests.test.ts +++ b/test/tests/asyncTests.test.ts @@ -1366,6 +1366,86 @@ describe('Run Apex tests asynchronously', () => { expect(handlerStub.calledOnce).to.be.true; }); }); + it('should still format async result with undefined test summary', async () => { + const asyncTestSrv = new AsyncTests(mockConnection); + const mockcheckRunResult = sandboxStub + .stub(asyncTestSrv, 'checkRunStatus') + .resolves(undefined); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); + const getAsyncTestResultsStub = sandboxStub + .stub(asyncTestSrv, 'getAsyncTestResults') + .resolves([ + { + done: true, + totalSize: 1, + records: [ + { + Id: '07Mxx00000F2Xx6UAF', + QueueItemId: '7092M000000Vt94QAC', + StackTrace: null, + Message: null, + AsyncApexJobId: testRunId, + MethodName: 'testLoggerLog', + Outcome: ApexTestResultOutcome.Skip, + ApexLogId: null, + ApexClass: { + Id: '01pxx00000O6tXZQAZ', + Name: 'TestLogger', + NamespacePrefix: 't3st', + FullName: 't3st__TestLogger' + }, + RunTime: null, + TestTimestamp: '3' + } + ] + } + ]); + const subscribeStub = sandboxStub + .stub(StreamingClient.prototype, 'subscribe') + .resolves({ + queueItem: { + done: true, + totalSize: 1, + records: [ + { + Status: ApexTestQueueItemStatus.Completed, + Id: 'xxx', + ApexClassId: 'xxxx', + TestRunResultId: 'xxx' + } + ] + } as ApexTestQueueItem, + runId: testRunId + }); + const handlerStub = sandboxStub.stub(StreamingClient.prototype, 'handler'); + sandboxStub.stub(StreamingClient.prototype, 'init'); + sandboxStub.stub(StreamingClient.prototype, 'handshake'); + + const results = await asyncTestSrv.reportAsyncResults(testRunId); + + expect(mockSingleRecordQuery.notCalled).to.be.true; + expect(mockcheckRunResult.calledTwice).to.be.true; + expect(getAsyncTestResultsStub.calledOnce).to.be.true; + expect(subscribeStub.called).to.be.true; + expect(handlerStub.notCalled).to.be.true; + + expect(results.summary.outcome === 'Unknown'); + expect(results.summary.userId === 'Unknown'); + expect(results.summary.testStartTime === ''); + expect(results.summary.testExecutionTimeInMs === 0); + expect(results.summary.testTotalTimeInMs === 0); + }); }); describe('elapsedTime', () => { From 86ba3c43c5c9edd6e05d078dc982d5cc2ca04eab Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Tue, 26 Mar 2024 14:05:43 -0600 Subject: [PATCH 3/6] chore: remove possibility of returning undefined --- src/tests/asyncTests.ts | 54 ++++++++-------- src/utils/elapsedTime.ts | 3 +- test/tests/asyncTests.test.ts | 116 ++++++---------------------------- 3 files changed, 48 insertions(+), 125 deletions(-) diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index 1493f0cb..a7265318 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -90,12 +90,12 @@ export class AsyncTests { } const asyncRunResult = await sClient.subscribe(undefined, testRunId); - const testRunSummary = await this.checkRunStatus(asyncRunResult.runId); + const runResult = await this.checkRunStatus(asyncRunResult.runId); return await this.formatAsyncResults( asyncRunResult, getCurrentTime(), codeCoverage, - testRunSummary, + runResult.testRunSummary, progress ); } catch (e) { @@ -120,13 +120,13 @@ export class AsyncTests { await sClient.init(); await sClient.handshake(); let queueItem: ApexTestQueueItem; - let testRunSummary = await this.checkRunStatus(testRunId); + let renResult = await this.checkRunStatus(testRunId); - if (testRunSummary !== undefined) { + if (renResult.testsComplete) { queueItem = await sClient.handler(undefined, testRunId); } else { queueItem = (await sClient.subscribe(undefined, testRunId)).queueItem; - testRunSummary = await this.checkRunStatus(testRunId); + renResult = await this.checkRunStatus(testRunId); } token && @@ -142,7 +142,7 @@ export class AsyncTests { { queueItem, runId: testRunId }, getCurrentTime(), codeCoverage, - testRunSummary + renResult.testRunSummary ); } catch (e) { throw formatTestErrors(e); @@ -153,7 +153,10 @@ export class AsyncTests { public async checkRunStatus( testRunId: string, progress?: Progress - ): Promise { + ): Promise<{ + testsComplete: boolean; + testRunSummary: ApexTestRunResult; + }> { if (!isValidTestRunID(testRunId)) { throw new Error(nls.localize('invalidTestRunIdErr', testRunId)); } @@ -167,21 +170,20 @@ export class AsyncTests { }); try { - const testRunSummaryResults = (await this.connection.singleRecordQuery( - testRunSummaryQuery, - { - tooling: true - } - )) as ApexTestRunResult; - - if (finishedStatuses.includes(testRunSummaryResults.Status)) { - return testRunSummaryResults; - } + const testRunSummaryResults = + await this.connection.singleRecordQuery( + testRunSummaryQuery, + { + tooling: true + } + ); + return { + testsComplete: finishedStatuses.includes(testRunSummaryResults.Status), + testRunSummary: testRunSummaryResults + }; } catch (e) { throw new Error(nls.localize('noTestResultSummary', testRunId)); } - - return undefined; } /** @@ -208,12 +210,12 @@ export class AsyncTests { const { apexTestClassIdSet, testResults, globalTests } = await this.buildAsyncTestResults(apexTestResults); - let outcome = testRunSummary?.Status ?? 'Unknown'; + let outcome = testRunSummary.Status; if (globalTests.failed > 0) { outcome = ApexTestRunResultStatus.Failed; } else if (globalTests.passed === 0) { outcome = ApexTestRunResultStatus.Skipped; - } else if (outcome === ApexTestRunResultStatus.Completed) { + } else if (testRunSummary.Status === ApexTestRunResultStatus.Completed) { outcome = ApexTestRunResultStatus.Passed; } @@ -228,17 +230,15 @@ export class AsyncTests { passRate: calculatePercentage(globalTests.passed, testResults.length), failRate: calculatePercentage(globalTests.failed, testResults.length), skipRate: calculatePercentage(globalTests.skipped, testResults.length), - testStartTime: testRunSummary?.StartTime - ? formatStartTime(testRunSummary.StartTime, 'ISO') - : '', - testExecutionTimeInMs: testRunSummary?.TestTime ?? 0, - testTotalTimeInMs: testRunSummary?.TestTime ?? 0, + testStartTime: formatStartTime(testRunSummary.StartTime, 'ISO'), + testExecutionTimeInMs: testRunSummary.TestTime ?? 0, + testTotalTimeInMs: testRunSummary.TestTime ?? 0, commandTimeInMs: getCurrentTime() - commandStartTime, hostname: this.connection.instanceUrl, orgId: this.connection.getAuthInfoFields().orgId, username: this.connection.getUsername(), testRunId: asyncRunResult.runId, - userId: testRunSummary?.UserId ?? 'Unknown' + userId: testRunSummary.UserId }, tests: testResults }; diff --git a/src/utils/elapsedTime.ts b/src/utils/elapsedTime.ts index 554c3b84..8a202d9f 100644 --- a/src/utils/elapsedTime.ts +++ b/src/utils/elapsedTime.ts @@ -7,8 +7,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return */ -import { Logger, LoggerLevel } from '@salesforce/core'; -import { LoggerLevelValue } from '@salesforce/core/lib/logger/logger'; +import { Logger, LoggerLevel, LoggerLevelValue } from '@salesforce/core'; const log = ( level: LoggerLevelValue, diff --git a/test/tests/asyncTests.test.ts b/test/tests/asyncTests.test.ts index f5749fbc..7fcb7cb6 100644 --- a/test/tests/asyncTests.test.ts +++ b/test/tests/asyncTests.test.ts @@ -38,7 +38,8 @@ import { ApexCodeCoverage, ApexTestQueueItemRecord, ResultFormat, - TestRunIdResult + TestRunIdResult, + ApexTestRunResult } from '../../src/tests/types'; import { AsyncTestRun, StreamingClient } from '../../src/streaming'; import { fail } from 'assert'; @@ -134,7 +135,10 @@ describe('Run Apex tests asynchronously', () => { sandboxStub .stub(StreamingClient.prototype, 'subscribe') .resolves(asyncResult); - sandboxStub.stub(AsyncTests.prototype, 'checkRunStatus'); + sandboxStub.stub(AsyncTests.prototype, 'checkRunStatus').resolves({ + testsComplete: true, + testRunSummary: {} as ApexTestRunResult + }); const testSrv = new TestService(mockConnection); const mockTestResultData = sandboxStub .stub(AsyncTests.prototype, 'formatAsyncResults') @@ -207,12 +211,12 @@ describe('Run Apex tests asynchronously', () => { } ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), undefined, - testRunSummary + runResult.testRunSummary ); let summaryQuery = @@ -326,12 +330,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); let summaryQuery = @@ -392,12 +396,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); expect(getTestResultData).to.deep.equals(diagnosticResult); @@ -448,12 +452,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); expect(getTestResultData).to.deep.equals(diagnosticFailure); @@ -468,12 +472,12 @@ describe('Run Apex tests asynchronously', () => { mockSingleRecordQuery.onFirstCall().throwsException('No records found'); try { - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); fail('Test should have thrown an error'); } catch (e) { @@ -569,12 +573,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexOrgWideCoverage); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), true, - testRunSummary + runResult.testRunSummary ); // verify summary data @@ -643,7 +647,7 @@ describe('Run Apex tests asynchronously', () => { report: reportStub }; - const testRunSummary = await asyncTestSrv.checkRunStatus( + const runResult = await asyncTestSrv.checkRunStatus( testRunId, progressReporter ); @@ -651,7 +655,7 @@ describe('Run Apex tests asynchronously', () => { { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), true, - testRunSummary, + runResult.testRunSummary, progressReporter ); @@ -1366,86 +1370,6 @@ describe('Run Apex tests asynchronously', () => { expect(handlerStub.calledOnce).to.be.true; }); }); - it('should still format async result with undefined test summary', async () => { - const asyncTestSrv = new AsyncTests(mockConnection); - const mockcheckRunResult = sandboxStub - .stub(asyncTestSrv, 'checkRunStatus') - .resolves(undefined); - const mockSingleRecordQuery = sandboxStub.stub( - mockConnection, - 'singleRecordQuery' - ); - sandboxStub.stub(mockConnection.tooling, 'query'); - mockSingleRecordQuery.onFirstCall().resolves({ - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - }); - const getAsyncTestResultsStub = sandboxStub - .stub(asyncTestSrv, 'getAsyncTestResults') - .resolves([ - { - done: true, - totalSize: 1, - records: [ - { - Id: '07Mxx00000F2Xx6UAF', - QueueItemId: '7092M000000Vt94QAC', - StackTrace: null, - Message: null, - AsyncApexJobId: testRunId, - MethodName: 'testLoggerLog', - Outcome: ApexTestResultOutcome.Skip, - ApexLogId: null, - ApexClass: { - Id: '01pxx00000O6tXZQAZ', - Name: 'TestLogger', - NamespacePrefix: 't3st', - FullName: 't3st__TestLogger' - }, - RunTime: null, - TestTimestamp: '3' - } - ] - } - ]); - const subscribeStub = sandboxStub - .stub(StreamingClient.prototype, 'subscribe') - .resolves({ - queueItem: { - done: true, - totalSize: 1, - records: [ - { - Status: ApexTestQueueItemStatus.Completed, - Id: 'xxx', - ApexClassId: 'xxxx', - TestRunResultId: 'xxx' - } - ] - } as ApexTestQueueItem, - runId: testRunId - }); - const handlerStub = sandboxStub.stub(StreamingClient.prototype, 'handler'); - sandboxStub.stub(StreamingClient.prototype, 'init'); - sandboxStub.stub(StreamingClient.prototype, 'handshake'); - - const results = await asyncTestSrv.reportAsyncResults(testRunId); - - expect(mockSingleRecordQuery.notCalled).to.be.true; - expect(mockcheckRunResult.calledTwice).to.be.true; - expect(getAsyncTestResultsStub.calledOnce).to.be.true; - expect(subscribeStub.called).to.be.true; - expect(handlerStub.notCalled).to.be.true; - - expect(results.summary.outcome === 'Unknown'); - expect(results.summary.userId === 'Unknown'); - expect(results.summary.testStartTime === ''); - expect(results.summary.testExecutionTimeInMs === 0); - expect(results.summary.testTotalTimeInMs === 0); - }); }); describe('elapsedTime', () => { From f6d9c6a18a6df469f818a8f47ea2c5092184f9ba Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Wed, 27 Mar 2024 13:05:50 -0600 Subject: [PATCH 4/6] chore: address review issues --- src/tests/asyncTests.ts | 4 ++-- src/tests/types.ts | 4 ++-- src/utils/dateUtil.ts | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index a7265318..696dbf46 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -191,7 +191,7 @@ export class AsyncTests { * @param asyncRunResult TestQueueItem and RunId for an async run * @param commandStartTime start time for the async test run * @param codeCoverage should report code coverages - * @param testRunSummary test run summary | undefined + * @param testRunSummary test run summary * @param progress progress reporter * @returns */ @@ -200,7 +200,7 @@ export class AsyncTests { asyncRunResult: AsyncTestRun, commandStartTime: number, codeCoverage = false, - testRunSummary: ApexTestRunResult | undefined, + testRunSummary: ApexTestRunResult, progress?: Progress ): Promise { const coveredApexClassIdSet = new Set(); diff --git a/src/tests/types.ts b/src/tests/types.ts index 0ca0de6f..3c7092ee 100644 --- a/src/tests/types.ts +++ b/src/tests/types.ts @@ -267,11 +267,11 @@ export type ApexTestRunResult = { /** * The time at which the test run started. */ - StartTime: string; + StartTime: string | undefined; /** * The time it took the test to run, in seconds. */ - TestTime: number; + TestTime: number | undefined; /** * The user who ran the test run */ diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts index 3a0eebd3..9ee222f2 100644 --- a/src/utils/dateUtil.ts +++ b/src/utils/dateUtil.ts @@ -18,9 +18,13 @@ export function getCurrentTime(): number { * @returns formatted date and time */ export function formatStartTime( - startTime: string | number, + startTime: string | number | undefined, format: 'ISO' | 'locale' = 'locale' ): string { + if (!startTime) { + return ''; + } + const date = new Date(startTime); if (format === 'ISO') { return date.toISOString(); From b1633801bb3ee5f752f9eb378fef11d8d8e77808 Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Wed, 27 Mar 2024 13:17:10 -0600 Subject: [PATCH 5/6] chore: fix typo --- src/tests/asyncTests.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index 696dbf46..7d7c91dc 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -120,13 +120,13 @@ export class AsyncTests { await sClient.init(); await sClient.handshake(); let queueItem: ApexTestQueueItem; - let renResult = await this.checkRunStatus(testRunId); + let runResult = await this.checkRunStatus(testRunId); - if (renResult.testsComplete) { + if (runResult.testsComplete) { queueItem = await sClient.handler(undefined, testRunId); } else { queueItem = (await sClient.subscribe(undefined, testRunId)).queueItem; - renResult = await this.checkRunStatus(testRunId); + runResult = await this.checkRunStatus(testRunId); } token && @@ -142,7 +142,7 @@ export class AsyncTests { { queueItem, runId: testRunId }, getCurrentTime(), codeCoverage, - renResult.testRunSummary + runResult.testRunSummary ); } catch (e) { throw formatTestErrors(e); From 6b7579e20c0fea16122d1265ebf49565baea02b7 Mon Sep 17 00:00:00 2001 From: Peter Hale Date: Thu, 28 Mar 2024 13:01:10 -0600 Subject: [PATCH 6/6] chore: merge main --- src/streaming/streamingClient.ts | 13 +- src/tests/asyncTests.ts | 75 +++---- src/tests/testService.ts | 8 +- test/tests/asyncTests.test.ts | 372 ++++++++++++++----------------- 4 files changed, 212 insertions(+), 256 deletions(-) diff --git a/src/streaming/streamingClient.ts b/src/streaming/streamingClient.ts index 1b9c7e0b..166b5db8 100644 --- a/src/streaming/streamingClient.ts +++ b/src/streaming/streamingClient.ts @@ -9,8 +9,8 @@ import { Client } from 'faye'; import { Connection, LoggerLevel } from '@salesforce/core'; import { RetrieveResultsInterval, - StreamingErrors, StreamMessage, + StreamingErrors, TestResultMessage } from './types'; import { Progress } from '../common'; @@ -19,7 +19,8 @@ import { elapsedTime, refreshAuth } from '../utils'; import { ApexTestProgressValue, ApexTestQueueItem, - ApexTestQueueItemRecord + ApexTestQueueItemRecord, + ApexTestQueueItemStatus } from '../tests/types'; const TEST_RESULT_CHANNEL = '/systemTopic/TestResult'; @@ -298,10 +299,10 @@ export class StreamingClient { if ( result.records.some( (item) => - item.Status === 'Queued' /* ApexTestQueueItemStatus.Queued */ || - item.Status === 'Holding' /* ApexTestQueueItemStatus.Holding */ || - item.Status === 'Preparing' /* ApexTestQueueItemStatus.Preparing */ || - item.Status === 'Processing' /* ApexTestQueueItemStatus.Processing */ + item.Status === ApexTestQueueItemStatus.Queued || + item.Status === ApexTestQueueItemStatus.Holding || + item.Status === ApexTestQueueItemStatus.Preparing || + item.Status === ApexTestQueueItemStatus.Processing ) ) { return null; diff --git a/src/tests/asyncTests.ts b/src/tests/asyncTests.ts index 899003dd..7cb4a88b 100644 --- a/src/tests/asyncTests.ts +++ b/src/tests/asyncTests.ts @@ -9,7 +9,7 @@ import { Connection } from '@salesforce/core'; import { CancellationToken, Progress } from '../common'; import { nls } from '../i18n'; import { AsyncTestRun, StreamingClient } from '../streaming'; -import { formatStartTime, getCurrentTime } from '../utils'; +import { elapsedTime, formatStartTime, getCurrentTime } from '../utils'; import { formatTestErrors, getAsyncDiagnostic } from './diagnosticUtil'; import { ApexTestProgressValue, @@ -20,7 +20,6 @@ import { ApexTestResultData, ApexTestResultOutcome, ApexTestRunResult, - ApexTestRunResultRecord, ApexTestRunResultStatus, AsyncTestArrayConfiguration, AsyncTestConfiguration, @@ -32,9 +31,16 @@ import * as util from 'util'; import { QUERY_RECORD_LIMIT } from './constants'; import { CodeCoverage } from './codeCoverage'; import { HttpRequest } from 'jsforce'; -import { elapsedTime } from '../utils/elapsedTime'; import { isValidTestRunID } from '../narrowing'; +const finishedStatuses = [ + ApexTestRunResultStatus.Aborted, + ApexTestRunResultStatus.Failed, + ApexTestRunResultStatus.Completed, + ApexTestRunResultStatus.Passed, + ApexTestRunResultStatus.Skipped +]; + export class AsyncTests { public readonly connection: Connection; private readonly codecoverage: CodeCoverage; @@ -83,12 +89,12 @@ export class AsyncTests { } const asyncRunResult = await sClient.subscribe(undefined, testRunId); - const testRunSummary = await this.checkRunStatus(asyncRunResult.runId); + const runResult = await this.checkRunStatus(asyncRunResult.runId); return await this.formatAsyncResults( asyncRunResult, getCurrentTime(), codeCoverage, - testRunSummary, + runResult.testRunSummary, progress ); } catch (e) { @@ -113,13 +119,13 @@ export class AsyncTests { await sClient.init(); await sClient.handshake(); let queueItem: ApexTestQueueItem; - let testRunSummary = await this.checkRunStatus(testRunId); + let runResult = await this.checkRunStatus(testRunId); - if (testRunSummary !== undefined) { + if (runResult.testsComplete) { queueItem = await sClient.handler(undefined, testRunId); } else { queueItem = (await sClient.subscribe(undefined, testRunId)).queueItem; - testRunSummary = await this.checkRunStatus(testRunId); + runResult = await this.checkRunStatus(testRunId); } token && @@ -135,7 +141,7 @@ export class AsyncTests { { queueItem, runId: testRunId }, getCurrentTime(), codeCoverage, - testRunSummary + runResult.testRunSummary ); } catch (e) { throw formatTestErrors(e); @@ -146,16 +152,15 @@ export class AsyncTests { public async checkRunStatus( testRunId: string, progress?: Progress - ): Promise { + ): Promise<{ + testsComplete: boolean; + testRunSummary: ApexTestRunResult; + }> { if (!isValidTestRunID(testRunId)) { throw new Error(nls.localize('invalidTestRunIdErr', testRunId)); } - let testRunSummaryQuery = - 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; - testRunSummaryQuery += - 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; - testRunSummaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; + const testRunSummaryQuery = `SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, MethodsEnqueued, StartTime, EndTime, TestTime, UserId FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; progress?.report({ type: 'FormatTestResultProgress', @@ -163,33 +168,21 @@ export class AsyncTests { message: nls.localize('retrievingTestRunSummary') }); - const testRunSummaryResults = (await this.connection.tooling.query( - testRunSummaryQuery, - { - autoFetch: true - } - )) as ApexTestRunResult; - - if (testRunSummaryResults.records.length === 0) { + try { + const testRunSummaryResults = + await this.connection.singleRecordQuery( + testRunSummaryQuery, + { + tooling: true + } + ); + return { + testsComplete: finishedStatuses.includes(testRunSummaryResults.Status), + testRunSummary: testRunSummaryResults + }; + } catch (e) { throw new Error(nls.localize('noTestResultSummary', testRunId)); } - - if ( - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Aborted || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Failed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Completed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Passed || - testRunSummaryResults.records[0].Status === - ApexTestRunResultStatus.Skipped - ) { - return testRunSummaryResults.records[0]; - } - - return undefined; } /** @@ -206,7 +199,7 @@ export class AsyncTests { asyncRunResult: AsyncTestRun, commandStartTime: number, codeCoverage = false, - testRunSummary: ApexTestRunResultRecord, + testRunSummary: ApexTestRunResult, progress?: Progress ): Promise { const coveredApexClassIdSet = new Set(); diff --git a/src/tests/testService.ts b/src/tests/testService.ts index d006184f..02227613 100644 --- a/src/tests/testService.ts +++ b/src/tests/testService.ts @@ -378,11 +378,9 @@ export class TestService { try { if (tests) { const payload = await this.buildTestPayload(tests); - const classes = payload.tests?.map((testItem) => { - if (testItem.className) { - return testItem.className; - } - }); + const classes = payload.tests + ?.filter((testItem) => testItem.className) + .map((testItem) => testItem.className); if (new Set(classes).size !== 1) { throw new Error(nls.localize('syncClassErr')); } diff --git a/test/tests/asyncTests.test.ts b/test/tests/asyncTests.test.ts index 474a1c86..4fee601f 100644 --- a/test/tests/asyncTests.test.ts +++ b/test/tests/asyncTests.test.ts @@ -135,7 +135,10 @@ describe('Run Apex tests asynchronously', () => { sandboxStub .stub(StreamingClient.prototype, 'subscribe') .resolves(asyncResult); - sandboxStub.stub(AsyncTests.prototype, 'checkRunStatus'); + sandboxStub.stub(AsyncTests.prototype, 'checkRunStatus').resolves({ + testsComplete: true, + testRunSummary: {} as ApexTestRunResult + }); const testSrv = new TestService(mockConnection); const mockTestResultData = sandboxStub .stub(AsyncTests.prototype, 'formatAsyncResults') @@ -171,22 +174,20 @@ describe('Run Apex tests asynchronously', () => { mockConnection.getAuthInfoFields().orgId; missingTimeTestData.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -210,19 +211,19 @@ describe('Run Apex tests asynchronously', () => { } ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), undefined, - testRunSummary + runResult.testRunSummary ); let summaryQuery = 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; summaryQuery += 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; summaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; - expect(mockToolingQuery.getCall(0).args[0]).to.equal(summaryQuery); + expect(mockSingleRecordQuery.getCall(0).args[0]).to.equal(summaryQuery); let testResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, '; testResultQuery += @@ -230,27 +231,25 @@ describe('Run Apex tests asynchronously', () => { testResultQuery += 'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix '; testResultQuery += `FROM ApexTestResult WHERE QueueItemId IN ('${pollResponse.records[0].Id}')`; - expect(mockToolingQuery.getCall(1).args[0]).to.equal(testResultQuery); + expect(mockToolingQuery.getCall(0).args[0]).to.equal(testResultQuery); expect(getTestResultData).to.deep.equals(missingTimeTestData); }); it('should report progress when checking test summary for run', async () => { const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); - mockToolingQuery.onSecondCall().resolves({ done: true, totalSize: 1, records: [ @@ -293,22 +292,20 @@ describe('Run Apex tests asynchronously', () => { skippedTestData.summary.orgId = mockConnection.getAuthInfoFields().orgId; skippedTestData.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -333,19 +330,19 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); let summaryQuery = 'SELECT AsyncApexJobId, Status, ClassesCompleted, ClassesEnqueued, '; summaryQuery += 'MethodsEnqueued, StartTime, EndTime, TestTime, UserId '; summaryQuery += `FROM ApexTestRunResult WHERE AsyncApexJobId = '${testRunId}'`; - expect(mockToolingQuery.getCall(0).args[0]).to.equal(summaryQuery); + expect(mockSingleRecordQuery.getCall(0).args[0]).to.equal(summaryQuery); let testResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, '; testResultQuery += @@ -353,7 +350,7 @@ describe('Run Apex tests asynchronously', () => { testResultQuery += 'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix '; testResultQuery += `FROM ApexTestResult WHERE QueueItemId IN ('${pollResponse.records[0].Id}')`; - expect(mockToolingQuery.getCall(1).args[0]).to.equal(testResultQuery); + expect(mockToolingQuery.getCall(0).args[0]).to.equal(testResultQuery); expect(getTestResultData).to.deep.equals(skippedTestData); }); @@ -361,22 +358,20 @@ describe('Run Apex tests asynchronously', () => { diagnosticResult.summary.orgId = mockConnection.getAuthInfoFields().orgId; diagnosticResult.summary.username = mockConnection.getUsername(); const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -401,12 +396,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); expect(getTestResultData).to.deep.equals(diagnosticResult); @@ -419,22 +414,20 @@ describe('Run Apex tests asynchronously', () => { diagnosticFailure.tests[0].diagnostic.exceptionStackTrace = undefined; diagnosticFailure.tests[0].stackTrace = undefined; const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onSecondCall().resolves({ + mockToolingQuery.onFirstCall().resolves({ done: true, totalSize: 1, records: [ @@ -459,12 +452,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexTestResult); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); expect(getTestResultData).to.deep.equals(diagnosticFailure); @@ -472,20 +465,19 @@ describe('Run Apex tests asynchronously', () => { it('should return an error if no test results are found', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().throwsException('No records found'); try { - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), false, - testRunSummary + runResult.testRunSummary ); fail('Test should have thrown an error'); } catch (e) { @@ -498,12 +490,11 @@ describe('Run Apex tests asynchronously', () => { it('should return an error if invalid test run id was provided', async () => { const invalidId = '000000xxxxx'; const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves(undefined); try { await asyncTestSrv.checkRunStatus(invalidId); @@ -512,19 +503,18 @@ describe('Run Apex tests asynchronously', () => { expect(e.message).to.equal( nls.localize('invalidTestRunIdErr', invalidId) ); - expect(mockToolingQuery.notCalled).to.be.true; + expect(mockSingleRecordQuery.notCalled).to.be.true; } }); it('should return an error if invalid test run id prefix was provided', async () => { const invalidId = '708000000xxxxxx'; const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 0, - records: [] - } as ApexTestRunResult); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves(undefined); try { await asyncTestSrv.checkRunStatus(invalidId); @@ -533,7 +523,7 @@ describe('Run Apex tests asynchronously', () => { expect(e.message).to.equal( nls.localize('invalidTestRunIdErr', invalidId) ); - expect(mockToolingQuery.notCalled).to.be.true; + expect(mockSingleRecordQuery.notCalled).to.be.true; } }); @@ -543,40 +533,37 @@ describe('Run Apex tests asynchronously', () => { mockConnection.tooling, 'query' ); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: '2020-07-12T02:54:47.000+0000', + TestTime: 1765, + UserId: '005xx000000abcDAAU' + }); mockToolingAutoQuery.onCall(0).resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: '2020-07-12T02:54:47.000+0000', - TestTime: 1765, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); - - mockToolingAutoQuery.onCall(1).resolves({ done: true, totalSize: 6, records: mixedTestResults } as ApexTestResult); - mockToolingAutoQuery.onCall(2).resolves({ + mockToolingAutoQuery.onCall(1).resolves({ done: true, totalSize: 3, records: mixedPerClassCodeCoverage } as ApexCodeCoverage); - mockToolingAutoQuery.onCall(3).resolves({ + mockToolingAutoQuery.onCall(2).resolves({ done: true, totalSize: 3, records: codeCoverageQueryResult } as ApexCodeCoverageAggregate); - mockToolingAutoQuery.onCall(4).resolves({ + mockToolingAutoQuery.onCall(3).resolves({ done: true, totalSize: 1, records: [ @@ -586,12 +573,12 @@ describe('Run Apex tests asynchronously', () => { ] } as ApexOrgWideCoverage); - const testRunSummary = await asyncTestSrv.checkRunStatus(testRunId); + const runResult = await asyncTestSrv.checkRunStatus(testRunId); const getTestResultData = await asyncTestSrv.formatAsyncResults( { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), true, - testRunSummary + runResult.testRunSummary ); // verify summary data @@ -614,40 +601,38 @@ describe('Run Apex tests asynchronously', () => { it('should report progress for aggregating code coverage', async () => { const asyncTestSrv = new AsyncTests(mockConnection); + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' + ); const mockToolingQuery = sandboxStub.stub(mockConnection.tooling, 'query'); - mockToolingQuery.onCall(0).resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: '2020-07-12T02:54:47.000+0000', - TestTime: 1765, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + mockSingleRecordQuery.onCall(0).resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: '2020-07-12T02:54:47.000+0000', + TestTime: 1765, + UserId: '005xx000000abcDAAU' + }); - mockToolingQuery.onCall(1).resolves({ + mockToolingQuery.onCall(0).resolves({ done: true, totalSize: 6, records: mixedTestResults } as ApexTestResult); - mockToolingQuery.onCall(2).resolves({ + mockToolingQuery.onCall(1).resolves({ done: true, totalSize: 3, records: mixedPerClassCodeCoverage } as ApexCodeCoverage); - mockToolingQuery.onCall(3).resolves({ + mockToolingQuery.onCall(2).resolves({ done: true, totalSize: 3, records: codeCoverageQueryResult } as ApexCodeCoverageAggregate); - mockToolingQuery.onCall(4).resolves({ + mockToolingQuery.onCall(3).resolves({ done: true, totalSize: 1, records: [ @@ -662,7 +647,7 @@ describe('Run Apex tests asynchronously', () => { report: reportStub }; - const testRunSummary = await asyncTestSrv.checkRunStatus( + const runResult = await asyncTestSrv.checkRunStatus( testRunId, progressReporter ); @@ -670,7 +655,7 @@ describe('Run Apex tests asynchronously', () => { { queueItem: pollResponse, runId: testRunId }, new Date().getTime(), true, - testRunSummary, + runResult.testRunSummary, progressReporter ); @@ -1226,39 +1211,28 @@ describe('Run Apex tests asynchronously', () => { describe('Report Test Run Status', async () => { it('should subscribe to test run for run still in progress', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery .onFirstCall() .resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Queued, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult) + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Queued, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }) .onSecondCall() .resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults' @@ -1289,7 +1263,7 @@ describe('Run Apex tests asynchronously', () => { await asyncTestSrv.reportAsyncResults(testRunId); - expect(mockToolingQuery.calledTwice).to.be.true; + expect(mockSingleRecordQuery.calledTwice).to.be.true; expect(formatResultsStub.calledOnce).to.be.true; expect(subscribeStub.calledOnce).to.be.true; expect(handlerStub.notCalled).to.be.true; @@ -1297,23 +1271,18 @@ describe('Run Apex tests asynchronously', () => { it('should query for test run results if run is complete', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults' @@ -1344,7 +1313,7 @@ describe('Run Apex tests asynchronously', () => { await asyncTestSrv.reportAsyncResults(testRunId); - expect(mockToolingQuery.calledOnce).to.be.true; + expect(mockSingleRecordQuery.calledOnce).to.be.true; expect(formatResultsStub.calledOnce).to.be.true; expect(subscribeStub.notCalled).to.be.true; expect(handlerStub.calledOnce).to.be.true; @@ -1352,23 +1321,18 @@ describe('Run Apex tests asynchronously', () => { it('should format results with retrieved test run summary', async () => { const asyncTestSrv = new AsyncTests(mockConnection); - const mockToolingQuery = sandboxStub.stub( - mockConnection.tooling, - 'query' + const mockSingleRecordQuery = sandboxStub.stub( + mockConnection, + 'singleRecordQuery' ); - mockToolingQuery.onFirstCall().resolves({ - done: true, - totalSize: 1, - records: [ - { - AsyncApexJobId: testRunId, - Status: ApexTestRunResultStatus.Completed, - StartTime: testStartTime, - TestTime: null, - UserId: '005xx000000abcDAAU' - } - ] - } as ApexTestRunResult); + sandboxStub.stub(mockConnection.tooling, 'query'); + mockSingleRecordQuery.onFirstCall().resolves({ + AsyncApexJobId: testRunId, + Status: ApexTestRunResultStatus.Completed, + StartTime: testStartTime, + TestTime: null, + UserId: '005xx000000abcDAAU' + }); const formatResultsStub = sandboxStub.stub( asyncTestSrv, 'formatAsyncResults'