diff --git a/packages/gh-action/src/ioc/setup-ioc-container.ts b/packages/gh-action/src/ioc/setup-ioc-container.ts index dfc5d78bb..4942f2c2a 100644 --- a/packages/gh-action/src/ioc/setup-ioc-container.ts +++ b/packages/gh-action/src/ioc/setup-ioc-container.ts @@ -10,20 +10,23 @@ import { GitHubIocTypes } from './gh-ioc-types'; import { CheckRunCreator } from '../check-run/check-run-creator'; import { GitHubArtifactsInfoProvider } from '../gh-artifacts-info-provider'; import { ConsoleCommentCreator } from '../console/console-comment-creator'; +import { JobSummaryCreator } from '../job-summary/job-summary-creator'; export function setupIocContainer(container = new inversify.Container({ autoBindInjectable: true })): inversify.Container { container = setupSharedIocContainer(container); container.bind(GitHubIocTypes.Github).toConstantValue(github); container.bind(iocTypes.TaskConfig).to(GHTaskConfig).inSingletonScope(); container.bind(CheckRunCreator).toSelf().inSingletonScope(); + container.bind(JobSummaryCreator).toSelf().inSingletonScope(); container.bind(ConsoleCommentCreator).toSelf().inSingletonScope(); container .bind(iocTypes.ProgressReporters) .toDynamicValue((context) => { const consoleCommentCreator = context.container.get(ConsoleCommentCreator); const checkRunCreator = context.container.get(CheckRunCreator); + const jobSummaryCreator = context.container.get(JobSummaryCreator); - return [checkRunCreator, consoleCommentCreator]; + return [checkRunCreator, consoleCommentCreator, jobSummaryCreator]; }) .inSingletonScope(); diff --git a/packages/gh-action/src/job-summary/job-summary-creator.spec.ts b/packages/gh-action/src/job-summary/job-summary-creator.spec.ts new file mode 100644 index 000000000..4df446554 --- /dev/null +++ b/packages/gh-action/src/job-summary/job-summary-creator.spec.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import 'reflect-metadata'; +import { IMock, Mock } from 'typemoq'; +import { Logger, ReportMarkdownConvertor } from '@accessibility-insights-action/shared'; +import { CombinedReportParameters } from 'accessibility-insights-report'; +import { JobSummaryCreator } from './job-summary-creator'; +import { GHTaskConfig } from '../task-config/gh-task-config'; + +describe(JobSummaryCreator, () => { + let testSubject: JobSummaryCreator; + let reportMarkdownConvertorMock: IMock; + let loggerMock: IMock; + let taskConfigMock: IMock; + + const markdownContent = 'test markdown content'; + const combinedReportResult = { serviceName: 'combinedReportResult' } as CombinedReportParameters; + + beforeEach(() => { + taskConfigMock = Mock.ofType(); + reportMarkdownConvertorMock = Mock.ofType(ReportMarkdownConvertor); + loggerMock = Mock.ofType(Logger); + testSubject = new JobSummaryCreator(taskConfigMock.object, reportMarkdownConvertorMock.object, loggerMock.object); + }); + + afterEach(() => { + reportMarkdownConvertorMock.verifyAll(); + loggerMock.verifyAll(); + }); + + describe('start', () => { + it('does nothing', async () => { + await expect(testSubject.start()).resolves.toBeUndefined(); + }); + }); + + describe('didScanSucceed', () => { + it('returns true by default', async () => { + await expect(testSubject.didScanSucceed()).resolves.toBe(true); + }); + + it('returns false after failRun() is called', async () => { + await testSubject.failRun(); + await expect(testSubject.didScanSucceed()).resolves.toBe(false); + }); + }); + + describe('completeRun', () => { + it('converts to markdown and writes the job summary', async () => { + reportMarkdownConvertorMock + .setup((a) => a.convert(combinedReportResult)) + .returns(() => markdownContent) + .verifiable(); + taskConfigMock + .setup((a) => a.writeJobSummary(markdownContent)) + .returns(() => Promise.resolve()) + .verifiable(); + await testSubject.completeRun(combinedReportResult); + }); + }); +}); diff --git a/packages/gh-action/src/job-summary/job-summary-creator.ts b/packages/gh-action/src/job-summary/job-summary-creator.ts new file mode 100644 index 000000000..1bb55c9f6 --- /dev/null +++ b/packages/gh-action/src/job-summary/job-summary-creator.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { iocTypes, Logger, ProgressReporter, ReportMarkdownConvertor } from '@accessibility-insights-action/shared'; +import { CombinedReportParameters } from 'accessibility-insights-report'; +import { GHTaskConfig } from '../task-config/gh-task-config'; + +@injectable() +export class JobSummaryCreator extends ProgressReporter { + private scanSucceeded = true; + + constructor( + @inject(iocTypes.TaskConfig) private readonly taskConfig: GHTaskConfig, + @inject(ReportMarkdownConvertor) private readonly reportMarkdownConvertor: ReportMarkdownConvertor, + @inject(Logger) private readonly logger: Logger, + ) { + super(); + } + + public start(): Promise { + this.logger.logDebug('job summary creator started'); + return Promise.resolve(); + } + + public async completeRun(combinedReportResult: CombinedReportParameters): Promise { + const reportMarkdown = this.reportMarkdownConvertor.convert(combinedReportResult); + return await this.taskConfig.writeJobSummary(reportMarkdown); + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async failRun(): Promise { + this.scanSucceeded = false; + } + + public didScanSucceed(): Promise { + return Promise.resolve(this.scanSucceeded); + } +} diff --git a/packages/gh-action/src/task-config/gh-task-config.spec.ts b/packages/gh-action/src/task-config/gh-task-config.spec.ts index 75124cc45..7013ef842 100644 --- a/packages/gh-action/src/task-config/gh-task-config.spec.ts +++ b/packages/gh-action/src/task-config/gh-task-config.spec.ts @@ -12,17 +12,20 @@ describe(GHTaskConfig, () => { let processStub: any; let actionCoreMock: IMock; let taskConfig: GHTaskConfig; + let markdownSummaryMock: IMock; beforeEach(() => { processStub = { env: {}, } as any; actionCoreMock = Mock.ofType(); + markdownSummaryMock = Mock.ofType(); taskConfig = new GHTaskConfig(processStub, actionCoreMock.object); }); afterEach(() => { actionCoreMock.verifyAll(); + markdownSummaryMock.verifyAll(); }); function getPlatformAgnosticPath(inputPath: string): string { @@ -126,4 +129,18 @@ describe(GHTaskConfig, () => { expect(actualRunId).toBe(runId); }); + + it('should write job summary', async () => { + const markdownStub = 'markdownStub'; + + markdownSummaryMock + .setup((o) => o.addRaw(markdownStub)) + .returns(() => markdownSummaryMock.object) + .verifiable(); + markdownSummaryMock.setup((o) => o.write()).verifiable(); + + actionCoreMock.setup((o) => o.summary).returns(() => markdownSummaryMock.object); + + await taskConfig.writeJobSummary(markdownStub); + }); }); diff --git a/packages/gh-action/src/task-config/gh-task-config.ts b/packages/gh-action/src/task-config/gh-task-config.ts index 999445cca..8a05659ce 100644 --- a/packages/gh-action/src/task-config/gh-task-config.ts +++ b/packages/gh-action/src/task-config/gh-task-config.ts @@ -98,6 +98,10 @@ export class GHTaskConfig extends TaskConfig { return keyToName[key]; } + public async writeJobSummary(jobSummaryMarkdown: string): Promise { + await this.actionCoreObj.summary.addRaw(jobSummaryMarkdown).write(); + } + public getUsageDocsUrl(): string { const url = 'https://github.com/microsoft/accessibility-insights-action/blob/main/docs/gh-action-usage.md'; return url;