| import { |
| ResultDbV1Client, |
| TestStatus, |
| } from '../resultdb-client'; |
| import { |
| DATA_SYMBOL, |
| } from '../checks-fetcher'; |
| import { |
| installChecksResult, |
| replaceTextArtifacts, |
| parseMarkdown, |
| } from '../checks-result'; |
| |
| function sanitizedAttr(attr: string) { |
| return `rel="noopener" target="_blank" ${attr}`; |
| } |
| |
| suite('checks-result tests', () => { |
| let sandbox: sinon.SinonSandbox; |
| const defaultMetaAttr = 'alt="chromeChecksMetadataTagDefault"'; |
| const greenMetaAttr = 'alt="chromeChecksMetadataTagGreen"'; |
| const redMetaAttr = 'alt="chromeChecksMetadataTagRed"'; |
| |
| setup(() => { |
| sandbox = sinon.createSandbox(); |
| sandbox.stub(ResultDbV1Client.prototype, 'getAuthorizationHeader') |
| .returns(Promise.resolve({'authorization': 'accessToken'})); |
| }); |
| |
| teardown(() => { |
| sandbox.restore(); |
| }); |
| |
| test('installChecksResult formats the CheckResult row', async () => { |
| const message = 'Run #1: unexpectedly failed.'; |
| |
| /* |
| * Recreate Gerrit endpoint DOM. |
| * |
| * <container> |
| * <shadow> |
| * <element> |
| * </element> |
| * </shadow> |
| * <messageDiv class="message"> |
| * message text |
| * </messageDiv> |
| * </container> |
| * |
| */ |
| const container = document.createElement('div'); |
| const shadow = container.attachShadow({mode: 'open'}); |
| const element = document.createElement('div'); |
| (element as any).result = {message: message}; |
| shadow.appendChild(element); |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = 'message'; |
| messageDiv.textContent = message; |
| container.appendChild(messageDiv); |
| |
| await installChecksResult(element); |
| |
| // Check element is styled. |
| const style = (element.getRootNode() as any).querySelector('style').textContent; |
| assert.isTrue(style.includes('word-break: break-all;')); |
| assert.isTrue(style.includes('white-space: pre-wrap;')); |
| |
| // Check original messageDiv removed. |
| assert.notStrictEqual(messageDiv.parentNode, container); |
| |
| // Check element's data is used to format the row. |
| const fetchStub = sandbox.stub(window, 'fetch'); |
| fetchStub.returns(Promise.resolve({ |
| ok: true, |
| text: async () => 'foo test failed!', |
| } as Response)); |
| |
| const artifactStub = sandbox.stub( |
| ResultDbV1Client.prototype, 'listArtifacts'); |
| artifactStub.returns(Promise.resolve({ |
| artifacts: [{ |
| name: 'invocations/123/foo/artifacts/baz', |
| artifactId: 'foo', |
| fetchUrl: 'https://foo.com', |
| fetchUrlExpiration: '2017-12-15T01:30:15.05Z', |
| sizeBytes: 10, |
| }], |
| })); |
| |
| (element as any).result[DATA_SYMBOL] = { |
| variant: {variant: {def: {builder: 'baz', os: 'foo-OS'}}}, |
| testResults: [ |
| { |
| result: { |
| name: 'invocations/123/foo', |
| status: TestStatus.FAIL, |
| summaryHtml: 'hello <text-artifact artifact-id="foo" />', |
| expected: false, |
| }, |
| }, |
| { |
| result: { |
| status: TestStatus.PASS, |
| summaryHtml: '', |
| expected: true, |
| }, |
| }, |
| ], |
| plugin: { |
| checks: () => ({ |
| updateResult: (_1: any, _2: any) => {}, |
| }), |
| }, |
| rdbHost: 'host', |
| }; |
| await installChecksResult(element); |
| assert.strictEqual( |
| element.innerHTML, |
| `<a ${sanitizedAttr(defaultMetaAttr)}><em>builder: baz, os: foo-OS</em></a>` + |
| '<ul>' + |
| '<li>' + |
| '<h3>' + |
| `<a ${sanitizedAttr(defaultMetaAttr)}>Run #1: </a>` + |
| `<a ${sanitizedAttr(redMetaAttr)}>unexpectedly failed</a>` + |
| '</h3>' + |
| 'hello foo test failed!' + |
| '</li>' + |
| '<li>' + |
| '<h3>' + |
| `<a ${sanitizedAttr(defaultMetaAttr)}>Run #2: </a>` + |
| `<a ${sanitizedAttr(greenMetaAttr)}>expectedly passed</a>` + |
| '</h3>' + |
| '</li>' + |
| '</ul>', |
| ); |
| |
| // Variants without definitions and artifacts without sizes are processed. |
| artifactStub.returns(Promise.resolve({ |
| artifacts: [ |
| { |
| name: 'invocations/123/baz/artifacts/bar', |
| artifactId: 'baz', |
| fetchUrl: 'https://baz.com', |
| fetchUrlExpiration: '2017-12-15T01:30:15.05Z', |
| sizeBytes: 0, |
| }, |
| ], |
| })); |
| |
| (element as any).result[DATA_SYMBOL] = { |
| testResults: [{ |
| result: { |
| name: 'invocations/123/baz', |
| status: TestStatus.FAIL, |
| summaryHtml: 'bye <text-artifact artifact-id="baz" />', |
| }, |
| }], |
| plugin: { |
| checks: () => ({ |
| updateResult: (_1: any, _2: any) => {}, |
| }), |
| }, |
| rdbHost: 'host', |
| }; |
| await installChecksResult(element); |
| assert.strictEqual( |
| element.innerHTML, |
| '<ul>' + |
| '<li>' + |
| '<h3>' + |
| `<a ${sanitizedAttr(defaultMetaAttr)}>Run #1: </a>` + |
| `<a ${sanitizedAttr(redMetaAttr)}>unexpectedly failed</a>` + |
| '</h3>' + |
| `bye <a ${sanitizedAttr(defaultMetaAttr)}>baz artifact is empty</a>` + |
| '</li>' + |
| '</ul>', |
| ); |
| |
| // Exonerations are shown. |
| (element as any).result[DATA_SYMBOL] = { |
| testResults: [{ |
| result: { |
| name: 'invocations/123/baz', |
| status: TestStatus.FAIL, |
| summaryHtml: 'bye <text-artifact artifact-id="baz" />', |
| }, |
| }], |
| testExonerations: [ |
| {explanationHtml: '<p>foo is exonerated</p>'}, |
| {explanationHtml: '<p>bar is exonerated</p>'}, |
| ], |
| plugin: { |
| checks: () => ({ |
| updateResult: (_1: any, _2: any) => {}, |
| }), |
| }, |
| rdbHost: 'host', |
| }; |
| await installChecksResult(element); |
| assert.strictEqual( |
| element.innerHTML, |
| '<br><br>' + |
| '<p>foo is exonerated</p><br><p>bar is exonerated</p>' + |
| '<ul>' + |
| '<li>' + |
| '<h3>' + |
| `<a ${sanitizedAttr(defaultMetaAttr)}>Run #1: </a>` + |
| `<a ${sanitizedAttr(redMetaAttr)}>unexpectedly failed</a>` + |
| '</h3>' + |
| `bye <a ${sanitizedAttr(defaultMetaAttr)}>baz artifact is empty</a>` + |
| '</li>' + |
| '</ul>', |
| ); |
| }); |
| |
| test('replaceTextArtifacts replaces <text-artifact/>', async () => { |
| const fetchStub = sandbox.stub(window, 'fetch'); |
| fetchStub.withArgs('https://foo.com/?n=50000').returns(Promise.resolve({ |
| ok: true, |
| text: async () => 'foo test failed!', |
| } as Response)); |
| fetchStub.withArgs('https://bar.org/?n=50000').returns(Promise.resolve({ |
| ok: true, |
| text: async () => 'bar test passed!', |
| } as Response)); |
| fetchStub.withArgs('https://baz.org/?n=50000').returns(Promise.reject( |
| new Error('bad fetch'))); |
| fetchStub.withArgs('https://boo.org/?n=50000').returns(Promise.resolve({ |
| ok: false, |
| status: 500, |
| text: async () => 'server error', |
| } as Response)); |
| |
| const resArtifacts = [ |
| { |
| artifactId: 'foo', |
| name: 'foo-name', |
| fetchUrl: 'https://foo.com/', |
| sizeBytes: 10, |
| }, |
| { |
| artifactId: 'baz', |
| name: 'baz-name', |
| fetchUrl: 'https://baz.org/', |
| sizeBytes: 10, |
| }, |
| { |
| artifactId: 'boo', |
| name: 'boo-name', |
| fetchUrl: 'https://boo.org/', |
| sizeBytes: 10, |
| }, |
| ]; |
| const invArtifacts = [{ |
| artifactId: 'bar', |
| name: 'bar-name', |
| fetchUrl: 'https://bar.org/', |
| sizeBytes: 10, |
| }]; |
| |
| // Replace and fetch artifacts. |
| assert.strictEqual( |
| await replaceTextArtifacts( |
| '<p><text-artifact artifact-id="foo" /></p>' + |
| '<p><text-artifact artifact-id="bar" inv-level /></p>' + |
| '<p><text-artifact artifact-id="baz" /></p>' + |
| '<p><text-artifact artifact-id="boo" /></p>', |
| resArtifacts, |
| invArtifacts, |
| ), |
| '<p>foo test failed!</p><p>bar test passed!</p>' + |
| `<p><a ${defaultMetaAttr}>Failed to fetch baz artifact: bad fetch. See console logs.</a></p>` + |
| `<p><a ${defaultMetaAttr}>Failed to fetch boo artifact: server error. See console logs.</a></p>`, |
| ); |
| }); |
| |
| test('replaceTextArtifacts caches successful fetches', async () => { |
| const fetchStub = sandbox.stub(window, 'fetch'); |
| fetchStub.onCall(0).returns(Promise.reject( |
| new Error('bad fetch'))); |
| fetchStub.onCall(1).returns(Promise.resolve({ |
| ok: true, |
| text: async () => 'foobar test failed!', |
| } as Response)); |
| fetchStub.onCall(2).returns(Promise.reject( |
| new Error('bad fetch'))); |
| |
| const artifacts = [{ |
| artifactId: 'foobar', |
| name: 'foobar-name', |
| fetchUrl: 'https://foobar.com/', |
| sizeBytes: 10, |
| }]; |
| |
| // Replace and fetch. |
| const html = '<p><text-artifact artifact-id="foobar" /></p>'; |
| assert.strictEqual( |
| await replaceTextArtifacts(html, artifacts, []), |
| `<p><a ${defaultMetaAttr}>Failed to fetch foobar artifact: bad fetch. See console logs.</a></p>`, |
| ); |
| |
| assert.strictEqual( |
| await replaceTextArtifacts(html, artifacts, []), |
| '<p>foobar test failed!</p>', |
| ); |
| sinon.assert.calledTwice((fetch as any)); |
| |
| // Check that fetches after the first successful fetch use cache. |
| assert.strictEqual( |
| await replaceTextArtifacts(html, artifacts, []), |
| '<p>foobar test failed!</p>', |
| ); |
| assert.strictEqual( |
| await replaceTextArtifacts(html, artifacts, []), |
| '<p>foobar test failed!</p>', |
| ); |
| sinon.assert.calledTwice((fetch as any)); |
| }); |
| |
| test('replaceTextArtifacts links to large text artifacts', async () => { |
| const fetchStub = sandbox.stub(window, 'fetch'); |
| const artifactOne = 'a'.repeat(50001); |
| fetchStub.withArgs('https://a1.com/?n=50000').returns(Promise.resolve({ |
| ok: true, |
| text: async () => artifactOne, |
| } as Response)); |
| const artifactTwo = 'a'.repeat(50002); |
| fetchStub.withArgs('https://a2.com/?n=50000').returns(Promise.resolve({ |
| ok: true, |
| text: async () => artifactTwo, |
| } as Response)); |
| |
| const artifacts = [ |
| { |
| artifactId: 'a1', |
| name: 'a1-name', |
| fetchUrl: 'https://a1.com/', |
| sizeBytes: artifactOne.length, |
| }, |
| { |
| artifactId: 'a2', |
| name: 'a2-name', |
| fetchUrl: 'https://a2.com/', |
| sizeBytes: artifactTwo.length, |
| }, |
| ]; |
| |
| assert.strictEqual( |
| await replaceTextArtifacts( |
| '<p><text-artifact artifact-id="a1"/></p>', artifacts, []), |
| `<p>${artifactOne}<a ${defaultMetaAttr}>\n\n...1 more byte...\n` + |
| 'More information in a1 artifact</a></p>', |
| ); |
| |
| assert.strictEqual( |
| await replaceTextArtifacts( |
| '<p><text-artifact artifact-id="a2"/></p>', artifacts, []), |
| `<p>${artifactTwo}<a ${defaultMetaAttr}>\n\n...2 more bytes...\n` + |
| 'More information in a2 artifact</a></p>', |
| ); |
| }); |
| |
| test('parseMarkdown replaces headers', () => { |
| assert.strictEqual(parseMarkdown('#foo'), '#foo'); |
| assert.strictEqual(parseMarkdown('# foo'), '<h1>foo</h1>'); |
| assert.strictEqual(parseMarkdown('## foo'), '<h2>foo</h2>'); |
| assert.strictEqual(parseMarkdown('### foo'), '<h3>foo</h3>'); |
| assert.strictEqual(parseMarkdown('#### foo'), '<h4>foo</h4>'); |
| assert.strictEqual(parseMarkdown('##### foo'), '<h5>foo</h5>'); |
| assert.strictEqual(parseMarkdown('###### foo'), '<h6>foo</h6>'); |
| assert.strictEqual(parseMarkdown('####### foo'), '####### foo'); |
| assert.strictEqual( |
| parseMarkdown(`# foo |
| ### bar |
| ## baz`), |
| '<h1>foo</h1><h3>bar</h3><h2>baz</h2>'); |
| assert.strictEqual( |
| parseMarkdown(`## foo __bar__`), |
| '<h2>foo <strong>bar</strong></h2>'); |
| assert.strictEqual( |
| parseMarkdown(`__## foo bar__`), |
| '<strong>## foo bar</strong>'); |
| }); |
| |
| test('parseMarkdown adds strong', () => { |
| assert.strictEqual(parseMarkdown('_foo__'), '_foo__'); |
| assert.strictEqual(parseMarkdown('__foo__'), '<strong>foo</strong>'); |
| assert.strictEqual(parseMarkdown('**foo__'), '**foo__'); |
| assert.strictEqual(parseMarkdown('** foo**'), '** foo**'); |
| assert.strictEqual(parseMarkdown('**foo **'), '**foo **'); |
| assert.strictEqual(parseMarkdown('**foo**'), '<strong>foo</strong>'); |
| assert.strictEqual(parseMarkdown('**fo**'), '<strong>fo</strong>'); |
| assert.strictEqual(parseMarkdown('**f**'), '<strong>f</strong>'); |
| assert.strictEqual( |
| parseMarkdown('**space here**'), |
| '<strong>space here</strong>'); |
| assert.strictEqual( |
| parseMarkdown('**newline\nhere**'), |
| '**newline\nhere**'); |
| assert.strictEqual( |
| parseMarkdown('this __is__ **foo**'), |
| 'this <strong>is</strong> <strong>foo</strong>'); |
| assert.strictEqual(parseMarkdown('____'), '____'); |
| }); |
| |
| test('parseMarkdown replaces links', () => { |
| assert.strictEqual(parseMarkdown('[foo](bar.com)'), |
| '<a href=\'bar.com\'>foo</a>'); |
| assert.strictEqual(parseMarkdown('[foo [baz]](bar.com)'), |
| '<a href=\'bar.com\'>foo [baz]</a>'); |
| assert.strictEqual(parseMarkdown('[](bar.com)'), |
| '<a href=\'bar.com\'></a>'); |
| assert.strictEqual(parseMarkdown('[a[c]](bar.com)[b[]](car.com)'), |
| '<a href=\'bar.com\'>a[c]</a><a href=\'car.com\'>b[]</a>'); |
| }); |
| |
| test('parseMarkdown creates unordered lists', () => { |
| const md = `# recipe |
| - **bacon** |
| - __lettuce__ |
| - tomatoes`; |
| assert.strictEqual(parseMarkdown(md), |
| '<h1>recipe</h1><ul>' + |
| '<li><strong>bacon</strong></li>' + |
| '<li><strong>lettuce</strong></li>' + |
| '<li>tomatoes</li></ul>'); |
| }); |
| |
| test('parseMarkdown ignores markdown in code blocks', () => { |
| const md = `\`\`\`syntax highlighting here |
| don't format __this__ |
| # or even this! |
| \`\`\` |
| but this __should__ be okay |
| # and this __too__`; |
| assert.strictEqual(parseMarkdown(md), |
| '<pre><code>don\'t format __this__\n# or even this!\n</code></pre>' + |
| 'but this <strong>should</strong> be okay' + |
| '\n<h1>and this <strong>too</strong></h1>'); |
| }); |
| }); |