blob: f66638694c063649ba00c9ba159d5a68ef468649 [file]
import './report-navigation-controller';
import '../window/window';
import { ReportNavigationController } from './report-navigation-controller';
import { Anomaly, GetGroupReportResponse } from '../json';
import { assert } from 'chai';
import fetchMock from 'fetch-mock';
import sinon from 'sinon';
const dummyAnomaly = (
id: string,
bugId: number,
startRev: number,
endRev: number,
testPath: string
): Anomaly => {
return {
id: id,
bug_id: bugId,
start_revision: startRev,
end_revision: endRev,
display_commit_number: endRev,
test_path: testPath,
is_improvement: false,
recovered: false,
state: 'new',
statistic: 'avg',
units: 'ms',
degrees_of_freedom: 0,
median_before_anomaly: 0,
median_after_anomaly: 0,
p_value: 0,
segment_size_after: 0,
segment_size_before: 0,
std_dev_before_anomaly: 0,
t_statistic: 0,
subscription_name: 'test',
bug_component: 'test',
bug_labels: [],
bug_cc_emails: [],
bisect_ids: [],
};
};
describe('ReportNavigationController', () => {
let controller: ReportNavigationController;
let host: any;
beforeEach(() => {
host = {
addController: sinon.spy(),
requestUpdate: sinon.spy(),
};
controller = new ReportNavigationController(host);
window.perf = { fetch_anomalies_from_sql: false } as any;
});
afterEach(() => {
fetchMock.reset();
sinon.restore();
delete (window as any).perf;
});
describe('openReportForAnomalyIds', () => {
it('opens URL with anomaly ID when single anomaly is provided', async () => {
const openStub = sinon.stub(window, 'open');
const anomalies = [dummyAnomaly('123', 0, 100, 200, 'test/path')];
await controller.openReportForAnomalyIds(anomalies);
assert.isTrue(openStub.calledWith('/u/?anomalyIDs=123', '_blank'));
});
it('opens URL with SID for multiple anomalies', async () => {
const openStub = sinon.stub(window, 'open');
const anomalies = [
dummyAnomaly('123', 0, 100, 200, 'test/path/1'),
dummyAnomaly('456', 0, 100, 200, 'test/path/2'),
];
fetchMock.post('/_/anomalies/group_report', {
sid: 'test_sid_12345',
anomaly_list: [],
selected_keys: [],
error: '',
timerange_map: null,
is_commit_number_based: false,
} as GetGroupReportResponse);
await controller.openReportForAnomalyIds(anomalies);
assert.isTrue(openStub.calledWith('/u/?sid=test_sid_12345', '_blank'));
assert.isTrue(fetchMock.called('/_/anomalies/group_report'));
});
it('falls back to anomalyIDs URL when SID is empty for multiple anomalies', async () => {
const openStub = sinon.stub(window, 'open');
const anomalies = [
dummyAnomaly('123', 0, 100, 200, 'test/path/1'),
dummyAnomaly('456', 0, 100, 200, 'test/path/2'),
];
fetchMock.post('/_/anomalies/group_report', {
sid: '',
anomaly_list: [],
selected_keys: [],
error: '',
timerange_map: null,
is_commit_number_based: false,
} as GetGroupReportResponse);
await controller.openReportForAnomalyIds(anomalies);
assert.isTrue(openStub.calledWith('/u/?anomalyIDs=123%2C456', '_blank'));
});
it('falls back to anomalyIDs URL when calculate_regr_shortcut returns empty SID (fetch_anomalies_from_sql)', async () => {
window.perf = { fetch_anomalies_from_sql: true } as any;
const openStub = sinon.stub(window, 'open');
const anomalies = [
dummyAnomaly('123', 0, 100, 200, 'test/path/1'),
dummyAnomaly('456', 0, 100, 200, 'test/path/2'),
];
fetchMock.post('/_/anomalies/calculate_regr_shortcut', {
sid: '',
});
await controller.openReportForAnomalyIds(anomalies);
assert.isTrue(openStub.calledWith('/u/?anomalyIDs=123%2C456', '_blank'));
});
it('handles fetch error gracefully', async () => {
const openStub = sinon.stub(window, 'open');
const anomalies = [
dummyAnomaly('123', 0, 100, 200, 'test/path/1'),
dummyAnomaly('456', 0, 100, 200, 'test/path/2'),
];
fetchMock.post('/_/anomalies/group_report', 500);
await controller.openReportForAnomalyIds(anomalies);
assert.isFalse(openStub.called);
});
});
describe('openMultiGraphUrl', () => {
it('generates and opens multi-graph URL', async () => {
const anomaly = dummyAnomaly('123', 0, 100, 200, 'test/path');
const newTab = {
location: { href: '' },
close: sinon.spy(),
} as unknown as Window;
fetchMock.post('begin:/_/anomalies/group_report', {
sid: 'sid',
timerange_map: {
'123': { begin: 1000, end: 2000 },
},
} as unknown as GetGroupReportResponse);
fetchMock.post('begin:/_/shortcut/update', { id: 'shortcut_id' });
await controller.openMultiGraphUrl(anomaly, newTab);
assert.isFalse(
(newTab.close as sinon.SinonSpy).called,
'newTab.close() should not be called'
);
assert.include(newTab.location.href, 'shortcut=shortcut_id');
assert.include(newTab.location.href, '/m/?begin=');
});
it('closes new tab on fetch failure', async () => {
const anomaly = dummyAnomaly('123', 0, 100, 200, 'test/path');
const newTab = {
location: { href: '' },
close: sinon.spy(),
} as unknown as Window;
fetchMock.post('/_/anomalies/group_report', 500);
await controller.openMultiGraphUrl(anomaly, newTab);
assert.isTrue((newTab.close as sinon.SinonSpy).calledOnce);
});
it('falls back to shift API if timerange is missing from group_report', async () => {
const anomaly = dummyAnomaly('123', 0, 100, 200, 'test/path');
const newTab = {
location: { href: '' },
close: sinon.spy(),
} as unknown as Window;
fetchMock.post('begin:/_/anomalies/group_report', {
sid: 'sid',
timerange_map: {}, // Empty, e.g. for dry-run anomaly
} as unknown as GetGroupReportResponse);
fetchMock.post('/_/shift', {
begin: 1000,
end: 2000,
});
fetchMock.post('begin:/_/shortcut/update', { id: 'shortcut_id' });
await controller.openMultiGraphUrl(anomaly, newTab, true);
assert.isFalse(
(newTab.close as sinon.SinonSpy).called,
'newTab.close() should not be called'
);
assert.include(newTab.location.href, 'shortcut=shortcut_id');
assert.include(newTab.location.href, '/m/?begin=-603800&end=693200');
});
});
});