Path: blob/main/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts
5299 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as sinon from 'sinon';6import assert from 'assert';7import * as uuid from '../../../../../base/common/uuid.js';8import {9IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, IExtensionTipsService, getTargetPlatform,10} from '../../../../../platform/extensionManagement/common/extensionManagement.js';11import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from '../../../../services/extensionManagement/common/extensionManagement.js';12import { ExtensionGalleryService } from '../../../../../platform/extensionManagement/common/extensionGalleryService.js';13import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';14import { Emitter, Event } from '../../../../../base/common/event.js';15import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';16import { NullTelemetryService } from '../../../../../platform/telemetry/common/telemetryUtils.js';17import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';18import { TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js';19import { TestContextService, TestProductService, TestStorageService } from '../../../../test/common/workbenchTestServices.js';20import { TestExtensionTipsService, TestSharedProcessService } from '../../../../test/electron-browser/workbenchTestServices.js';21import { TestNotificationService } from '../../../../../platform/notification/test/common/testNotificationService.js';22import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';23import { URI } from '../../../../../base/common/uri.js';24import { testWorkspace } from '../../../../../platform/workspace/test/common/testWorkspace.js';25import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';26import { IPager } from '../../../../../base/common/paging.js';27import { getGalleryExtensionId } from '../../../../../platform/extensionManagement/common/extensionManagementUtil.js';28import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';29import { ConfigurationKey, IExtensionsWorkbenchService } from '../../common/extensions.js';30import { TestExtensionEnablementService } from '../../../../services/extensionManagement/test/browser/extensionEnablementService.test.js';31import { IURLService } from '../../../../../platform/url/common/url.js';32import { ITextModel } from '../../../../../editor/common/model.js';33import { IModelService } from '../../../../../editor/common/services/model.js';34import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';35import { INotificationService, Severity, IPromptChoice, IPromptOptions } from '../../../../../platform/notification/common/notification.js';36import { NativeURLService } from '../../../../../platform/url/common/urlService.js';37import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';38import { ExtensionType } from '../../../../../platform/extensions/common/extensions.js';39import { ISharedProcessService } from '../../../../../platform/ipc/electron-browser/services.js';40import { FileService } from '../../../../../platform/files/common/fileService.js';41import { NullLogService, ILogService } from '../../../../../platform/log/common/log.js';42import { IFileService } from '../../../../../platform/files/common/files.js';43import { IProductService } from '../../../../../platform/product/common/productService.js';44import { ExtensionRecommendationsService } from '../../browser/extensionRecommendationsService.js';45import { NoOpWorkspaceTagsService } from '../../../tags/browser/workspaceTagsService.js';46import { IWorkspaceTagsService } from '../../../tags/common/workspaceTags.js';47import { ExtensionsWorkbenchService } from '../../browser/extensionsWorkbenchService.js';48import { IExtensionService } from '../../../../services/extensions/common/extensions.js';49import { IWorkspaceExtensionsConfigService, WorkspaceExtensionsConfigService } from '../../../../services/extensionRecommendations/common/workspaceExtensionsConfig.js';50import { IExtensionIgnoredRecommendationsService } from '../../../../services/extensionRecommendations/common/extensionRecommendations.js';51import { ExtensionIgnoredRecommendationsService } from '../../../../services/extensionRecommendations/common/extensionIgnoredRecommendationsService.js';52import { IExtensionRecommendationNotificationService } from '../../../../../platform/extensionRecommendations/common/extensionRecommendations.js';53import { ExtensionRecommendationNotificationService } from '../../browser/extensionRecommendationNotificationService.js';54import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';55import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js';56import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js';57import { joinPath } from '../../../../../base/common/resources.js';58import { VSBuffer } from '../../../../../base/common/buffer.js';59import { platform } from '../../../../../base/common/platform.js';60import { arch } from '../../../../../base/common/process.js';61import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';62import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';63import { DisposableStore } from '../../../../../base/common/lifecycle.js';64import { timeout } from '../../../../../base/common/async.js';65import { IUpdateService, State } from '../../../../../platform/update/common/update.js';66import { IUriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentity.js';67import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js';68import { IMeteredConnectionService } from '../../../../../platform/meteredConnection/common/meteredConnection.js';6970const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });7172const mockExtensionGallery: IGalleryExtension[] = [73aGalleryExtension('MockExtension1', {74displayName: 'Mock Extension 1',75version: '1.5',76publisherId: 'mockPublisher1Id',77publisher: 'mockPublisher1',78publisherDisplayName: 'Mock Publisher 1',79description: 'Mock Description',80installCount: 1000,81rating: 4,82ratingCount: 10083}, {84dependencies: ['pub.1'],85}, {86manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },87readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },88changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },89download: { uri: 'uri:download', fallbackUri: 'fallback:download' },90icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },91license: { uri: 'uri:license', fallbackUri: 'fallback:license' },92repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },93signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },94coreTranslations: []95}),96aGalleryExtension('MockExtension2', {97displayName: 'Mock Extension 2',98version: '1.5',99publisherId: 'mockPublisher2Id',100publisher: 'mockPublisher2',101publisherDisplayName: 'Mock Publisher 2',102description: 'Mock Description',103installCount: 1000,104rating: 4,105ratingCount: 100106}, {107dependencies: ['pub.1', 'pub.2'],108}, {109manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },110readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },111changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },112download: { uri: 'uri:download', fallbackUri: 'fallback:download' },113icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },114license: { uri: 'uri:license', fallbackUri: 'fallback:license' },115repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },116signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },117coreTranslations: []118})119];120121const mockExtensionLocal = [122{123type: ExtensionType.User,124identifier: mockExtensionGallery[0].identifier,125manifest: {126name: mockExtensionGallery[0].name,127publisher: mockExtensionGallery[0].publisher,128version: mockExtensionGallery[0].version129},130metadata: null,131path: 'somepath',132readmeUrl: 'some readmeUrl',133changelogUrl: 'some changelogUrl'134},135{136type: ExtensionType.User,137identifier: mockExtensionGallery[1].identifier,138manifest: {139name: mockExtensionGallery[1].name,140publisher: mockExtensionGallery[1].publisher,141version: mockExtensionGallery[1].version142},143metadata: null,144path: 'somepath',145readmeUrl: 'some readmeUrl',146changelogUrl: 'some changelogUrl'147}148];149150const mockTestData = {151recommendedExtensions: [152'mockPublisher1.mockExtension1',153'MOCKPUBLISHER2.mockextension2',154'badlyformattedextension',155'MOCKPUBLISHER2.mockextension2',156'unknown.extension'157],158validRecommendedExtensions: [159'mockPublisher1.mockExtension1',160'MOCKPUBLISHER2.mockextension2'161]162};163164function aPage<T>(...objects: T[]): IPager<T> {165return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };166}167168const noAssets: IGalleryExtensionAssets = {169changelog: null,170download: null!,171icon: null!,172license: null,173manifest: null,174readme: null,175repository: null,176signature: null,177coreTranslations: []178};179180function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension {181const targetPlatform = getTargetPlatform(platform, arch);182const galleryExtension = <IGalleryExtension>Object.create({ name, publisher: 'pub', version: '1.0.0', allTargetPlatforms: [targetPlatform], properties: {}, assets: {}, ...properties });183galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], targetPlatform, ...galleryExtensionProperties };184galleryExtension.assets = { ...galleryExtension.assets, ...assets };185galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: uuid.generateUuid() };186return <IGalleryExtension>galleryExtension;187}188189suite('ExtensionRecommendationsService Test', () => {190let disposableStore: DisposableStore;191let workspaceService: IWorkspaceContextService;192let instantiationService: TestInstantiationService;193let testConfigurationService: TestConfigurationService;194let testObject: ExtensionRecommendationsService;195let prompted: boolean;196let promptedEmitter: Emitter<void>;197let onModelAddedEvent: Emitter<ITextModel>;198199teardown(async () => {200disposableStore.dispose();201await timeout(0); // allow for async disposables to complete202});203204ensureNoDisposablesAreLeakedInTestSuite();205206setup(() => {207disposableStore = new DisposableStore();208instantiationService = disposableStore.add(new TestInstantiationService());209promptedEmitter = disposableStore.add(new Emitter<void>());210instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);211instantiationService.stub(ISharedProcessService, TestSharedProcessService);212instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService()));213testConfigurationService = new TestConfigurationService();214instantiationService.stub(IConfigurationService, testConfigurationService);215instantiationService.stub(IProductService, TestProductService);216instantiationService.stub(ILogService, NullLogService);217const fileService = new FileService(instantiationService.get(ILogService));218instantiationService.stub(IFileService, disposableStore.add(fileService));219const fileSystemProvider = disposableStore.add(new InMemoryFileSystemProvider());220disposableStore.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));221instantiationService.stub(IUriIdentityService, disposableStore.add(new UriIdentityService(instantiationService.get(IFileService))));222instantiationService.stub(INotificationService, new TestNotificationService());223instantiationService.stub(IContextKeyService, new MockContextKeyService());224instantiationService.stub(IWorkbenchExtensionManagementService, {225onInstallExtension: Event.None,226onDidInstallExtensions: Event.None,227onUninstallExtension: Event.None,228onDidUninstallExtension: Event.None,229onDidUpdateExtensionMetadata: Event.None,230onDidChangeProfile: Event.None,231onProfileAwareDidInstallExtensions: Event.None,232async getInstalled() { return []; },233async canInstall() { return true; },234async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [], publisherMapping: {} }; },235async getTargetPlatform() { return getTargetPlatform(platform, arch); },236});237instantiationService.stub(IExtensionService, {238onDidChangeExtensions: Event.None,239extensions: [],240async whenInstalledExtensionsRegistered() { return true; }241});242instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService)));243instantiationService.stub(ITelemetryService, NullTelemetryService);244instantiationService.stub(IURLService, NativeURLService);245instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService());246instantiationService.stub(IStorageService, disposableStore.add(new TestStorageService()));247instantiationService.stub(ILogService, new NullLogService());248instantiationService.stub(IProductService, {249extensionRecommendations: {250'ms-python.python': {251onFileOpen: [252{253'pathGlob': '{**/*.py}',254important: true255}256]257},258'ms-vscode.PowerShell': {259onFileOpen: [260{261'pathGlob': '{**/*.ps,**/*.ps1}',262important: true263}264]265},266'ms-dotnettools.csharp': {267onFileOpen: [268{269'pathGlob': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',270}271]272},273'msjsdiag.debugger-for-chrome': {274onFileOpen: [275{276'pathGlob': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}',277}278]279},280'lukehoban.Go': {281onFileOpen: [282{283'pathGlob': '**/*.go',284}285]286}287},288});289290instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });291instantiationService.stub(IMeteredConnectionService, { isConnectionMetered: false, onDidChangeIsConnectionMetered: Event.None });292instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));293instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService)));294295onModelAddedEvent = new Emitter<ITextModel>();296297instantiationService.stub(IEnvironmentService, {});298instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);299instantiationService.stub(IExtensionGalleryService, 'isEnabled', true);300instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage<IGalleryExtension>(...mockExtensionGallery));301instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', mockExtensionGallery);302303prompted = false;304305class TestNotificationService2 extends TestNotificationService {306public override prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) {307prompted = true;308promptedEmitter.fire();309return super.prompt(severity, message, choices, options);310}311}312313instantiationService.stub(INotificationService, new TestNotificationService2());314315testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false });316instantiationService.stub(IModelService, <IModelService>{317getModels(): any { return []; },318onModelAdded: onModelAddedEvent.event319});320});321322function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {323return setUpFolder(folderName, recommendedExtensions, ignoredRecommendations);324}325326async function setUpFolder(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {327const fileService = instantiationService.get(IFileService);328const folderDir = joinPath(ROOT, folderName);329const workspaceSettingsDir = joinPath(folderDir, '.vscode');330await fileService.createFolder(workspaceSettingsDir);331const configPath = joinPath(workspaceSettingsDir, 'extensions.json');332await fileService.writeFile(configPath, VSBuffer.fromString(JSON.stringify({333'recommendations': recommendedExtensions,334'unwantedRecommendations': ignoredRecommendations,335}, null, '\t')));336337const myWorkspace = testWorkspace(folderDir);338339instantiationService.stub(IFileService, fileService);340workspaceService = new TestContextService(myWorkspace);341instantiationService.stub(IWorkspaceContextService, workspaceService);342instantiationService.stub(IWorkspaceExtensionsConfigService, disposableStore.add(instantiationService.createInstance(WorkspaceExtensionsConfigService)));343instantiationService.stub(IExtensionIgnoredRecommendationsService, disposableStore.add(instantiationService.createInstance(ExtensionIgnoredRecommendationsService)));344instantiationService.stub(IExtensionRecommendationNotificationService, disposableStore.add(instantiationService.createInstance(ExtensionRecommendationNotificationService)));345}346347function testNoPromptForValidRecommendations(recommendations: string[]) {348return setUpFolderWorkspace('myFolder', recommendations).then(() => {349testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));350return testObject.activationPromise.then(() => {351assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length);352assert.ok(!prompted);353});354});355}356357function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) {358return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {359testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));360assert.ok(!prompted);361362return testObject.getWorkspaceRecommendations().then(() => {363assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0);364assert.ok(!prompted);365});366});367}368369test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations when galleryService is absent', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {370const galleryQuerySpy = sinon.spy();371instantiationService.stub(IExtensionGalleryService, { query: galleryQuerySpy, isEnabled: () => false });372373return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions)374.then(() => assert.ok(galleryQuerySpy.notCalled));375}));376377test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations during extension development', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {378instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')], isExtensionDevelopment: true });379return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions);380}));381382test('ExtensionRecommendationsService: No workspace recommendations or prompts when extensions.json has empty array', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {383return testNoPromptForValidRecommendations([]);384}));385386test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {387await setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions);388testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));389390await Event.toPromise(promptedEmitter.event);391const recommendations = Object.keys(testObject.getAllRecommendationsWithReason());392const expected = [...mockTestData.validRecommendedExtensions, 'unknown.extension'];393assert.strictEqual(recommendations.length, expected.length);394expected.forEach(x => {395assert.strictEqual(recommendations.indexOf(x.toLowerCase()) > -1, true);396});397}));398399test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {400instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);401return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);402}));403404test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {405instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);406return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions.map(x => x.toUpperCase()));407}));408409test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {410testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true });411return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);412}));413414test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {415testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true });416return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {417testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));418return testObject.activationPromise.then(() => {419assert.ok(!prompted);420});421});422}));423424test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {425instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);426return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);427}));428429test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {430instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);431instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.PROFILE, StorageTarget.MACHINE);432instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.PROFILE, StorageTarget.MACHINE);433434return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {435testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));436return testObject.activationPromise.then(() => {437const recommendations = testObject.getAllRecommendationsWithReason();438assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been globally ignored439assert.ok(recommendations['ms-python.python']); // stored recommendation440assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation441assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been globally ignored442});443});444}));445446test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {447const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation.448const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';449instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);450instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);451452return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => {453testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));454return testObject.activationPromise.then(() => {455const recommendations = testObject.getAllRecommendationsWithReason();456assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been workspace ignored457assert.ok(recommendations['ms-python.python']); // stored recommendation458assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation459assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been workspace ignored460});461});462}));463464test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {465466const storageService = instantiationService.get(IStorageService);467const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation.468const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';469const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.470storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);471storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);472storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);473474await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations);475testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));476await testObject.activationPromise;477478const recommendations = testObject.getAllRecommendationsWithReason();479assert.deepStrictEqual(Object.keys(recommendations), ['ms-python.python', 'mockpublisher1.mockextension1']);480}));481482test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {483const storageService = instantiationService.get(IStorageService);484485const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';486const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.487storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);488storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);489storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);490491await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions);492const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);493testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));494await testObject.activationPromise;495496let recommendations = testObject.getAllRecommendationsWithReason();497assert.ok(recommendations['ms-python.python']);498assert.ok(recommendations['mockpublisher1.mockextension1']);499assert.ok(!recommendations['mockpublisher2.mockextension2']);500501extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', true);502503recommendations = testObject.getAllRecommendationsWithReason();504assert.ok(recommendations['ms-python.python']);505assert.ok(!recommendations['mockpublisher1.mockextension1']);506assert.ok(!recommendations['mockpublisher2.mockextension2']);507508extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', false);509510recommendations = testObject.getAllRecommendationsWithReason();511assert.ok(recommendations['ms-python.python']);512assert.ok(recommendations['mockpublisher1.mockextension1']);513assert.ok(!recommendations['mockpublisher2.mockextension2']);514}));515516test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {517const storageService = instantiationService.get(IStorageService);518const changeHandlerTarget = sinon.spy();519const ignoredExtensionId = 'Some.Extension';520521storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);522storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.PROFILE, StorageTarget.MACHINE);523524await setUpFolderWorkspace('myFolder', []);525testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));526const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);527disposableStore.add(extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget));528extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true);529await testObject.activationPromise;530531assert.ok(changeHandlerTarget.calledOnce);532assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: ignoredExtensionId.toLowerCase(), isRecommended: false }));533}));534535test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {536const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';537instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);538539return setUpFolderWorkspace('myFolder', []).then(() => {540testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));541return testObject.activationPromise.then(() => {542const recommendations = testObject.getFileBasedRecommendations();543assert.strictEqual(recommendations.length, 2);544assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips545assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips546assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips547});548});549}));550551test('ExtensionRecommendationsService: Get file based recommendations from storage (new format)', async () => {552const milliSecondsInADay = 1000 * 60 * 60 * 24;553const now = Date.now();554const tenDaysOld = 10 * milliSecondsInADay;555const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`;556instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);557558await setUpFolderWorkspace('myFolder', []);559testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));560await testObject.activationPromise;561562const recommendations = testObject.getFileBasedRecommendations();563assert.strictEqual(recommendations.length, 2);564assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips565assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips566assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips567assert.ok(recommendations.every(extensionId => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week568});569});570571572