Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle namespaces when running tests #126

Merged
merged 6 commits into from
Feb 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/apex-node/src/i18n/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,7 @@ export const messages = {
class_tested_header: 'CLASS BEING TESTED',
uncovered_lines_col_header: 'UNCOVERED LINES',
code_cov_header: 'Apex Code Coverage by Class',
detailed_code_cov_header: 'Apex Code Coverage for Test Run %s'
detailed_code_cov_header: 'Apex Code Coverage for Test Run %s',
syncClassErr:
'Synchronous test runs can include test methods from only one Apex class. Omit the --synchronous flag or include tests from only one class'
Comment on lines +67 to +69
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When are keys underscore vs camelcase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm good question, looking through right now and looks like we've got a bit of everything : - ) I'll update the ones in the library package for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'll open a follow up for everything, it's going to get messy in this PR

};
137 changes: 135 additions & 2 deletions packages/apex-node/src/tests/testService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import {
PerClassCoverage,
OutputDirConfig,
ApexTestResultRecord,
SyncTestFailure
SyncTestFailure,
TestItem,
TestLevel,
NamespaceQueryResult
} from './types';
import * as util from 'util';
import { nls } from '../i18n';
Expand All @@ -38,14 +41,144 @@ import { ApexDiagnostic } from '../utils/types';
// Tooling API query char limit is 100,000 after v48; REST API limit for uri + headers is 16,348 bytes
// local testing shows query char limit to be closer to ~12,400
const QUERY_CHAR_LIMIT = 12400;

const CLASS_ID_PREFIX = '01p';
export class TestService {
public readonly connection: Connection;

constructor(connection: Connection) {
this.connection = connection;
}

// utils to build test run payloads that may contain namespaces
public async buildSyncPayload(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this functionality over to the library- we'll need these utils for the plugin and in VS Code. Consumers can still build their own payloads when calling runTestsAsync/Sync but these utils are helpful if there's a chance that namespace info may be present.

testLevel: TestLevel,
tests?: string,
classnames?: string
): Promise<SyncTestConfiguration> {
let payload: SyncTestConfiguration;
if (tests) {
payload = await this.buildTestPayload(tests);
const classes = payload.tests?.map(testItem => {
if (testItem.className) {
return testItem.className;
}
});
if (new Set(classes).size !== 1) {
return Promise.reject(new Error(nls.localize('syncClassErr')));
}
} else {
const prop = classnames.toLowerCase().startsWith(CLASS_ID_PREFIX)
? 'classId'
: 'className';
payload = {
tests: [{ [prop]: classnames }],
testLevel
};
}
return payload;
}

public async buildAsyncPayload(
testLevel: TestLevel,
tests?: string,
classNames?: string,
suiteNames?: string
): Promise<AsyncTestConfiguration | AsyncTestArrayConfiguration> {
if (tests) {
return (await this.buildTestPayload(
tests
)) as AsyncTestArrayConfiguration;
} else if (classNames) {
return await this.buildAsyncClassPayload(classNames);
} else {
return {
suiteNames,
testLevel
};
}
}

private async buildTestPayload(
testNames: string
): Promise<AsyncTestArrayConfiguration | SyncTestConfiguration> {
const testNameArray = testNames.split(',');
const testItems: TestItem[] = [];
let namespaces: Set<string>;

for (const test of testNameArray) {
if (test.indexOf('.') > 0) {
const testParts = test.split('.');
if (testParts.length === 3) {
testItems.push({
namespace: `${testParts[0]}`,
className: `${testParts[1]}`,
testMethods: [testParts[2]]
});
} else {
if (typeof namespaces === 'undefined') {
namespaces = await this.queryNamespaces();
}

if (namespaces.has(testParts[0])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will error out if we run a namespaced tests against an org that returns no namespaces.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind it does work correctly. Can we add a test for that scenario ?

Copy link
Contributor Author

@AnanyaJha AnanyaJha Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - tests that the argument we build will treat the namespace as a classname & the class as a test method so eventually the test run would error out.

testItems.push({
namespace: `${testParts[0]}`,
className: `${testParts[1]}`
});
} else {
testItems.push({
className: testParts[0],
testMethods: [testParts[1]]
});
}
}
} else {
testItems.push({ className: test });
}
}

return {
tests: testItems,
testLevel: TestLevel.RunSpecifiedTests
};
}

private async buildAsyncClassPayload(
classNames: string
): Promise<AsyncTestArrayConfiguration> {
const classNameArray = classNames.split(',') as string[];
const classItems = classNameArray.map(item => {
const classParts = item.split('.');
if (classParts.length > 1) {
return {
namespace: `${classParts[0]}`,
className: `${classParts[1]}`
};
}
return { className: item } as TestItem;
});
return { tests: classItems, testLevel: TestLevel.RunSpecifiedTests };
}

public async queryNamespaces(): Promise<Set<string>> {
const installedNsQuery = 'SELECT NamespacePrefix FROM PackageLicense';
const installedNsResult = (await this.connection.query(
installedNsQuery
)) as NamespaceQueryResult;
const installedNamespaces = installedNsResult.records.map(record => {
return record.NamespacePrefix;
});

const orgNsQuery = 'SELECT NamespacePrefix FROM Organization';
const orgNsResult = (await this.connection.query(
orgNsQuery
)) as NamespaceQueryResult;
const orgNamespaces = orgNsResult.records.map(record => {
return record.NamespacePrefix;
});

return new Set([...orgNamespaces, ...installedNamespaces]);
}

// Synchronous Test Runs
public async runTestSynchronous(
options: SyncTestConfiguration,
Expand Down
12 changes: 12 additions & 0 deletions packages/apex-node/src/tests/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export type TestItem = {
* Array of test names to run. Not specifying it will run all test methods in a test class
*/
testMethods?: string[];
/**
* Namespace associated with the test class or method
*/
namespace?: string;
};

export type AsyncTestArrayConfiguration = {
Expand Down Expand Up @@ -441,3 +445,11 @@ export type ApexOrgWideCoverage = {
totalSize: number;
records: { PercentCovered: string }[];
};

export type NamespaceRecord = {
NamespacePrefix: string;
};

export type NamespaceQueryResult = {
records: NamespaceRecord[];
};
Loading