blob: 23aa75c86405b2683c375cde4fe39416efa9b546 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import type * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
import {setupLocaleHooks} from '../../testing/LocaleHelpers.js';
import {MockIssuesModel} from '../../testing/MockIssuesModel.js';
import * as IssuesManager from './issues_manager.js';
describeWithEnvironment('SelectivePermissionsInterventionIssue', () => {
setupLocaleHooks();
const mockModel = new MockIssuesModel([]) as unknown as SDK.IssuesModel.IssuesModel;
const mockTarget = {
id: () => 'fake-id',
isDisposed: () => false,
model: () => null,
};
mockModel.target = () => mockTarget as unknown as SDK.Target.Target;
function createProtocolIssueWithDetails(
selectivePermissionsInterventionIssueDetails: Protocol.Audits.SelectivePermissionsInterventionIssueDetails):
Protocol.Audits.InspectorIssue {
return {
code: Protocol.Audits.InspectorIssueCode.SelectivePermissionsInterventionIssue,
details: {selectivePermissionsInterventionIssueDetails},
};
}
const issueDetails = {
apiName: 'geolocation',
stackTrace: {
callFrames: [
{
functionName: 'foo',
scriptId: '1' as Protocol.Runtime.ScriptId,
url: 'https://example.com/foo.js',
lineNumber: 10,
columnNumber: 5,
},
],
},
adAncestry: {
ancestryChain: [
{
scriptId: '2' as Protocol.Runtime.ScriptId,
debuggerId: '123' as Protocol.Runtime.UniqueDebuggerId,
name: 'https://ads.com/ad.js',
},
],
rootScriptFilterlistRule: '||ads.com^',
},
};
it('correctly creates an issue with valid details', () => {
const issue = createProtocolIssueWithDetails(issueDetails);
const interventionIssues =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue);
assert.lengthOf(interventionIssues, 1);
const interventionIssue = interventionIssues[0];
assert.strictEqual(
interventionIssue.getCategory(), IssuesManager.Issue.IssueCategory.SELECTIVE_PERMISSIONS_INTERVENTION);
assert.strictEqual(interventionIssue.getKind(), IssuesManager.Issue.IssueKind.PAGE_ERROR);
const description = interventionIssue.getDescription();
assert.strictEqual(description?.file, 'selectivePermissionsIntervention.md');
assert.lengthOf(description?.links || [], 1);
assert.strictEqual(description?.links[0].link, 'https://crbug.com/435223477');
assert.deepEqual(Array.from([interventionIssue.details()]), [issueDetails]);
});
it('generates a stable primary key', () => {
const issue = createProtocolIssueWithDetails(issueDetails);
const interventionIssue1 =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue)[0];
const interventionIssue2 =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue)[0];
assert.strictEqual(interventionIssue1.primaryKey(), interventionIssue2.primaryKey());
});
it('generates different primary keys for different details', () => {
const issue1 = createProtocolIssueWithDetails(issueDetails);
const issue2 = createProtocolIssueWithDetails({...issueDetails, apiName: 'microphone'});
const interventionIssue1 =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue1)[0];
const interventionIssue2 =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue2)[0];
assert.notStrictEqual(interventionIssue1.primaryKey(), interventionIssue2.primaryKey());
});
it('handles missing optional fields', () => {
const issueDetailsMinimal = {
apiName: 'geolocation',
adAncestry: {
ancestryChain: [],
},
} as Protocol.Audits.SelectivePermissionsInterventionIssueDetails;
const issue = createProtocolIssueWithDetails(issueDetailsMinimal);
const interventionIssues =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue);
assert.lengthOf(interventionIssues, 1);
const interventionIssue = interventionIssues[0];
assert.deepEqual(Array.from([interventionIssue.details()]), [issueDetailsMinimal]);
});
it('returns an empty array when details are missing', () => {
const issue = {
code: Protocol.Audits.InspectorIssueCode.SelectivePermissionsInterventionIssue,
details: {},
};
const interventionIssues =
IssuesManager.SelectivePermissionsInterventionIssue.SelectivePermissionsInterventionIssue.fromInspectorIssue(
mockModel, issue);
assert.lengthOf(interventionIssues, 0);
});
});