blob: bd38d29aacb9cf639e703a7cf2d3e711f97b4e24 [file]
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>');
});
});