Skip to content
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
24 changes: 24 additions & 0 deletions src/helpers/report-builder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ class ReportBuilder extends ReportBuilderBase {
const missingConfigByFile = new Map();

for (const [, { data: detail }] of this._data.details) {
this.#applyDefaultTaxonomy(detail);

const { status, retries } = detail;

if (status === 'passed') {
Expand Down Expand Up @@ -449,6 +451,28 @@ class ReportBuilder extends ReportBuilderBase {
return this;
}

#applyDefaultTaxonomy(detail) {
if (detail.location?.file != null) {
return;
}

const { type, tool } = this.#reportConfiguration.getDefaultTaxonomy();

if (type == null && tool == null) {
return;
}

detail.taxonomy ??= {};

if (type != null) {
detail.taxonomy.type ??= type;
}

if (tool != null) {
detail.taxonomy.tool ??= tool;
}
}

#logMissingConfigWarnings(missingConfigByFile) {
if (missingConfigByFile.size === 0) {
return;
Expand Down
14 changes: 9 additions & 5 deletions src/helpers/report-configuration.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ class ReportConfiguration {
return this.#reportConfigurationPath;
}

getDefaultTaxonomy() {
return {
type: this.#reportConfiguration.type?.toLowerCase(),
tool: this.#reportConfiguration.tool
};
}

getTaxonomy(filePath) {
filePath = makeRelativeFilePath(filePath);

Expand All @@ -124,12 +131,9 @@ class ReportConfiguration {
}
}

const {
type: defaultType,
tool: defaultTool
} = this.#reportConfiguration;
const { type: defaultType, tool: defaultTool } = this.getDefaultTaxonomy();

metadata.type = metadata.type ?? defaultType?.toLowerCase();
metadata.type = metadata.type ?? defaultType;
metadata.tool = metadata.tool ?? defaultTool;

return metadata;
Expand Down
17 changes: 17 additions & 0 deletions src/helpers/report-configuration.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Logger } from './report-builder.js';

export interface Taxonomy {
type?: string;
tool?: string;
}

export declare class ReportConfiguration {
constructor(path?: string, logger?: Pick<Logger, 'warning'>);

getPath(): string | undefined;
getDefaultTaxonomy(): Taxonomy;
getTaxonomy(filePath: string): Taxonomy;
hasTaxonomy(filePath: string): boolean;
ignoreFilePath(filePath: string): boolean;
toJSON(): Record<string, unknown>;
}
23 changes: 23 additions & 0 deletions test/integration/report-validation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getOperatingSystemType } from '../../src/helpers/system.cjs';
import { hasContext } from '../../src/helpers/github.cjs';
import { latestReportVersion } from '../../src/helpers/schema.cjs';
import { Report } from '../../src/helpers/report.cjs';
import { ReportBuilder } from '../../src/helpers/report-builder.cjs';
import { testReportLatestPartial as testReportLatestPartialJest } from './data/validation/test-report-jest.js';
import { testReportLatestPartial as testReportLatestPartialMocha } from './data/validation/test-report-mocha.js';
import { testReportLatestPartial as testReportLatestPartialNodeTest } from './data/validation/test-report-node.js';
Expand Down Expand Up @@ -61,6 +62,28 @@ const reportTests = [{
}];

describe('report validation', () => {
it('applies available defaults to details without locations', () => {
const warnings = [];
const logger = {
error: () => {},
info: () => {},
location: () => {},
warning: message => warnings.push(message)
};
const builder = new ReportBuilder('node', logger, { reportWriter: () => {} });
const detail = builder.getDetail('locationless').setPassed();

builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({ tool: 'Test Tooling' });
expect(warnings).to.deep.equal([
'1 test missing taxonomy fields: type (1).',
'Affected files: 1:',
'- unknown location (1 test)',
'Check d2l-test-reporting.config.json to configure missing taxonomy fields.'
]);
});

for (const reportTest of reportTests) {
describe(reportTest.name, () => {
it('exists', () => {
Expand Down
78 changes: 78 additions & 0 deletions test/unit/report-builder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,84 @@ describe('report builder', () => {
});
});

describe('taxonomy', () => {
it('applies defaults to a detail without a location', () => {
const builder = new ReportBuilder('mocha', noopLogger, { reportWriter: () => { } });
const detail = builder.getDetail('test');

builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({ type: 'unit', tool: 'Test Reporting' });
});

it('does not override file taxonomy with defaults', () => {
const builder = new ReportBuilder('mocha', noopLogger, { reportWriter: () => { } });
const detail = builder.getDetail('test');

detail.setLocationFile('test/example.test.js');
builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({ type: 'unit', tool: 'Test Reporting' });
});

it('applies available default taxonomy fields', () => {
mock.method(fs, 'readFileSync', () => JSON.stringify({ type: 'unit' }));

const builder = new ReportBuilder('mocha', noopLogger, {
reportConfigurationPath: './d2l-test-reporting.config.json',
reportWriter: () => { }
});
const detail = builder.getDetail('test');

builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({ type: 'unit' });
});

it('applies a tool-only default', () => {
mock.method(fs, 'readFileSync', () => JSON.stringify({ tool: 'Test Reporting' }));

const builder = new ReportBuilder('mocha', noopLogger, {
reportConfigurationPath: './d2l-test-reporting.config.json',
reportWriter: () => { }
});
const detail = builder.getDetail('test');

builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({ tool: 'Test Reporting' });
});

it('preserves existing taxonomy fields', () => {
const builder = new ReportBuilder('mocha', noopLogger, { reportWriter: () => { } });
const detail = builder.getDetail('test');

detail.data.taxonomy = { type: 'custom', tool: 'custom' };
builder.finalize();

expect(detail.data.taxonomy).to.deep.equal({
type: 'custom',
tool: 'custom'
});
});

it('does not create taxonomy without defaults', () => {
mock.method(fs, 'readFileSync', () => JSON.stringify({
overrides: [{ pattern: '**', type: 'unit', tool: 'Test Reporting' }]
}));

const builder = new ReportBuilder('mocha', noopLogger, {
reportConfigurationPath: './d2l-test-reporting.config.json',
reportWriter: () => { }
});
const detail = builder.getDetail('test');

builder.finalize();

expect(detail.data).to.not.have.property('taxonomy');
});
});

describe('ignore', () => {
it('false without config', () => {
const builder = new ReportBuilder('mocha', noopLogger, { reportWriter: () => { } });
Expand Down
50 changes: 50 additions & 0 deletions test/unit/report-configuration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ describe('report configuration', () => {

expect(() => new ReportConfiguration(configPath, logger)).to.throw('Unable to read/parse');
});

it('when the default configuration is unparseable', () => {
mock.method(fs, 'readFileSync', () => 'not json');

expect(() => new ReportConfiguration(undefined, logger)).to.throw('Unable to read/parse');
});
});

it('empty without config file', () => {
Expand All @@ -152,6 +158,12 @@ describe('report configuration', () => {
});
});

it('reports the resolved configuration path', () => {
const config = loadConfig({ type: 'integration', tool: 'Test Reporting' });

expect(config.getPath()).to.equal('d2l-test-reporting.config.json');
});

describe('default logger', () => {
const legacyConfig = {
type: 'integration',
Expand All @@ -172,6 +184,28 @@ describe('report configuration', () => {
});

describe('taxonomy', () => {
describe('defaults', () => {
it('lowercases type and preserves tool', () => {
const config = loadConfig({ type: 'UI', tool: 'My Tool' });

expect(config.getDefaultTaxonomy()).to.deep.equal({
type: 'ui',
tool: 'My Tool'
});
});

it('omits absent values', () => {
const config = loadConfig({
overrides: [{ pattern: '**', type: 'unit', tool: 'Test Reporting' }]
});

expect(config.getDefaultTaxonomy()).to.deep.equal({
type: undefined,
tool: undefined
});
});
});

it('lowercases type', () => {
const config = loadConfig({ type: 'UI', tool: 'My Tool' });

Expand All @@ -198,6 +232,22 @@ describe('report configuration', () => {
});
});

it('inherits missing fields from defaults', () => {
const config = loadConfig({
type: 'integration',
tool: 'Default Tool',
overrides: [{
pattern: '**/special.test.js',
type: 'UI'
}]
});

expect(config.getTaxonomy('test/special.test.js')).to.deep.equal({
type: 'ui',
tool: 'Default Tool'
});
});

it('normalizes leading ./', () => {
const config = loadConfig({
type: 'integration',
Expand Down
27 changes: 27 additions & 0 deletions test/unit/report.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,33 @@ const testReportOldV3ConfigOnly = {
describe('report', () => {
afterEach(() => mock.reset());

describe('LMS information', () => {
const lmsInfo = {
buildNumber: '20.26.9.12345',
instanceUrl: 'https://example.brightspace.com'
};
const reports = [{
name: 'v1',
report: testReportV1Full
}, {
name: 'v2',
report: testReportV2Full
}, {
name: 'v3',
report: testReportLatestFull
}];

for (const { name, report: reportData } of reports) {
it(`adds LMS information to ${name} reports`, () => {
mock.method(fs, 'readFileSync', () => JSON.stringify(reportData));

const report = new Report(testReportPath, { lmsInfo });

expect(report.toJSON().summary.lms).to.deep.equal(lmsInfo);
});
}
});

describe(`legacy (v1, upgrades to v${latestReportVersion})`, () => {
const testReportCurrentVersion = 1;

Expand Down