Path: blob/main/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts
3296 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';6869const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });7071const mockExtensionGallery: IGalleryExtension[] = [72aGalleryExtension('MockExtension1', {73displayName: 'Mock Extension 1',74version: '1.5',75publisherId: 'mockPublisher1Id',76publisher: 'mockPublisher1',77publisherDisplayName: 'Mock Publisher 1',78description: 'Mock Description',79installCount: 1000,80rating: 4,81ratingCount: 10082}, {83dependencies: ['pub.1'],84}, {85manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },86readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },87changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },88download: { uri: 'uri:download', fallbackUri: 'fallback:download' },89icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },90license: { uri: 'uri:license', fallbackUri: 'fallback:license' },91repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },92signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },93coreTranslations: []94}),95aGalleryExtension('MockExtension2', {96displayName: 'Mock Extension 2',97version: '1.5',98publisherId: 'mockPublisher2Id',99publisher: 'mockPublisher2',100publisherDisplayName: 'Mock Publisher 2',101description: 'Mock Description',102installCount: 1000,103rating: 4,104ratingCount: 100105}, {106dependencies: ['pub.1', 'pub.2'],107}, {108manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },109readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },110changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },111download: { uri: 'uri:download', fallbackUri: 'fallback:download' },112icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },113license: { uri: 'uri:license', fallbackUri: 'fallback:license' },114repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },115signature: { uri: 'uri:signature', fallbackUri: 'fallback:signature' },116coreTranslations: []117})118];119120const mockExtensionLocal = [121{122type: ExtensionType.User,123identifier: mockExtensionGallery[0].identifier,124manifest: {125name: mockExtensionGallery[0].name,126publisher: mockExtensionGallery[0].publisher,127version: mockExtensionGallery[0].version128},129metadata: null,130path: 'somepath',131readmeUrl: 'some readmeUrl',132changelogUrl: 'some changelogUrl'133},134{135type: ExtensionType.User,136identifier: mockExtensionGallery[1].identifier,137manifest: {138name: mockExtensionGallery[1].name,139publisher: mockExtensionGallery[1].publisher,140version: mockExtensionGallery[1].version141},142metadata: null,143path: 'somepath',144readmeUrl: 'some readmeUrl',145changelogUrl: 'some changelogUrl'146}147];148149const mockTestData = {150recommendedExtensions: [151'mockPublisher1.mockExtension1',152'MOCKPUBLISHER2.mockextension2',153'badlyformattedextension',154'MOCKPUBLISHER2.mockextension2',155'unknown.extension'156],157validRecommendedExtensions: [158'mockPublisher1.mockExtension1',159'MOCKPUBLISHER2.mockextension2'160]161};162163function aPage<T>(...objects: T[]): IPager<T> {164return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null! };165}166167const noAssets: IGalleryExtensionAssets = {168changelog: null,169download: null!,170icon: null!,171license: null,172manifest: null,173readme: null,174repository: null,175signature: null,176coreTranslations: []177};178179function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension {180const targetPlatform = getTargetPlatform(platform, arch);181const galleryExtension = <IGalleryExtension>Object.create({ name, publisher: 'pub', version: '1.0.0', allTargetPlatforms: [targetPlatform], properties: {}, assets: {}, ...properties });182galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], targetPlatform, ...galleryExtensionProperties };183galleryExtension.assets = { ...galleryExtension.assets, ...assets };184galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: uuid.generateUuid() };185return <IGalleryExtension>galleryExtension;186}187188suite('ExtensionRecommendationsService Test', () => {189let disposableStore: DisposableStore;190let workspaceService: IWorkspaceContextService;191let instantiationService: TestInstantiationService;192let testConfigurationService: TestConfigurationService;193let testObject: ExtensionRecommendationsService;194let prompted: boolean;195let promptedEmitter: Emitter<void>;196let onModelAddedEvent: Emitter<ITextModel>;197198teardown(async () => {199disposableStore.dispose();200await timeout(0); // allow for async disposables to complete201});202203ensureNoDisposablesAreLeakedInTestSuite();204205setup(() => {206disposableStore = new DisposableStore();207instantiationService = disposableStore.add(new TestInstantiationService());208promptedEmitter = disposableStore.add(new Emitter<void>());209instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);210instantiationService.stub(ISharedProcessService, TestSharedProcessService);211instantiationService.stub(ILifecycleService, disposableStore.add(new TestLifecycleService()));212testConfigurationService = new TestConfigurationService();213instantiationService.stub(IConfigurationService, testConfigurationService);214instantiationService.stub(IProductService, TestProductService);215instantiationService.stub(ILogService, NullLogService);216const fileService = new FileService(instantiationService.get(ILogService));217instantiationService.stub(IFileService, disposableStore.add(fileService));218const fileSystemProvider = disposableStore.add(new InMemoryFileSystemProvider());219disposableStore.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));220instantiationService.stub(IUriIdentityService, disposableStore.add(new UriIdentityService(instantiationService.get(IFileService))));221instantiationService.stub(INotificationService, new TestNotificationService());222instantiationService.stub(IContextKeyService, new MockContextKeyService());223instantiationService.stub(IWorkbenchExtensionManagementService, {224onInstallExtension: Event.None,225onDidInstallExtensions: Event.None,226onUninstallExtension: Event.None,227onDidUninstallExtension: Event.None,228onDidUpdateExtensionMetadata: Event.None,229onDidChangeProfile: Event.None,230onProfileAwareDidInstallExtensions: Event.None,231async getInstalled() { return []; },232async canInstall() { return true; },233async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [], publisherMapping: {} }; },234async getTargetPlatform() { return getTargetPlatform(platform, arch); },235});236instantiationService.stub(IExtensionService, {237onDidChangeExtensions: Event.None,238extensions: [],239async whenInstalledExtensionsRegistered() { return true; }240});241instantiationService.stub(IWorkbenchExtensionEnablementService, disposableStore.add(new TestExtensionEnablementService(instantiationService)));242instantiationService.stub(ITelemetryService, NullTelemetryService);243instantiationService.stub(IURLService, NativeURLService);244instantiationService.stub(IWorkspaceTagsService, new NoOpWorkspaceTagsService());245instantiationService.stub(IStorageService, disposableStore.add(new TestStorageService()));246instantiationService.stub(ILogService, new NullLogService());247instantiationService.stub(IProductService, {248extensionRecommendations: {249'ms-python.python': {250onFileOpen: [251{252'pathGlob': '{**/*.py}',253important: true254}255]256},257'ms-vscode.PowerShell': {258onFileOpen: [259{260'pathGlob': '{**/*.ps,**/*.ps1}',261important: true262}263]264},265'ms-dotnettools.csharp': {266onFileOpen: [267{268'pathGlob': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}',269}270]271},272'msjsdiag.debugger-for-chrome': {273onFileOpen: [274{275'pathGlob': '{**/*.ts,**/*.tsx,**/*.js,**/*.jsx,**/*.es6,**/*.mjs,**/*.cjs,**/.babelrc}',276}277]278},279'lukehoban.Go': {280onFileOpen: [281{282'pathGlob': '**/*.go',283}284]285}286},287});288289instantiationService.stub(IUpdateService, { onStateChange: Event.None, state: State.Uninitialized });290instantiationService.set(IExtensionsWorkbenchService, disposableStore.add(instantiationService.createInstance(ExtensionsWorkbenchService)));291instantiationService.stub(IExtensionTipsService, disposableStore.add(instantiationService.createInstance(TestExtensionTipsService)));292293onModelAddedEvent = new Emitter<ITextModel>();294295instantiationService.stub(IEnvironmentService, {});296instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []);297instantiationService.stub(IExtensionGalleryService, 'isEnabled', true);298instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage<IGalleryExtension>(...mockExtensionGallery));299instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', mockExtensionGallery);300301prompted = false;302303class TestNotificationService2 extends TestNotificationService {304public override prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) {305prompted = true;306promptedEmitter.fire();307return super.prompt(severity, message, choices, options);308}309}310311instantiationService.stub(INotificationService, new TestNotificationService2());312313testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false });314instantiationService.stub(IModelService, <IModelService>{315getModels(): any { return []; },316onModelAdded: onModelAddedEvent.event317});318});319320function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {321return setUpFolder(folderName, recommendedExtensions, ignoredRecommendations);322}323324async function setUpFolder(folderName: string, recommendedExtensions: string[], ignoredRecommendations: string[] = []): Promise<void> {325const fileService = instantiationService.get(IFileService);326const folderDir = joinPath(ROOT, folderName);327const workspaceSettingsDir = joinPath(folderDir, '.vscode');328await fileService.createFolder(workspaceSettingsDir);329const configPath = joinPath(workspaceSettingsDir, 'extensions.json');330await fileService.writeFile(configPath, VSBuffer.fromString(JSON.stringify({331'recommendations': recommendedExtensions,332'unwantedRecommendations': ignoredRecommendations,333}, null, '\t')));334335const myWorkspace = testWorkspace(folderDir);336337instantiationService.stub(IFileService, fileService);338workspaceService = new TestContextService(myWorkspace);339instantiationService.stub(IWorkspaceContextService, workspaceService);340instantiationService.stub(IWorkspaceExtensionsConfigService, disposableStore.add(instantiationService.createInstance(WorkspaceExtensionsConfigService)));341instantiationService.stub(IExtensionIgnoredRecommendationsService, disposableStore.add(instantiationService.createInstance(ExtensionIgnoredRecommendationsService)));342instantiationService.stub(IExtensionRecommendationNotificationService, disposableStore.add(instantiationService.createInstance(ExtensionRecommendationNotificationService)));343}344345function testNoPromptForValidRecommendations(recommendations: string[]) {346return setUpFolderWorkspace('myFolder', recommendations).then(() => {347testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));348return testObject.activationPromise.then(() => {349assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length);350assert.ok(!prompted);351});352});353}354355function testNoPromptOrRecommendationsForValidRecommendations(recommendations: string[]) {356return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {357testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));358assert.ok(!prompted);359360return testObject.getWorkspaceRecommendations().then(() => {361assert.strictEqual(Object.keys(testObject.getAllRecommendationsWithReason()).length, 0);362assert.ok(!prompted);363});364});365}366367test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations when galleryService is absent', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {368const galleryQuerySpy = sinon.spy();369instantiationService.stub(IExtensionGalleryService, { query: galleryQuerySpy, isEnabled: () => false });370371return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions)372.then(() => assert.ok(galleryQuerySpy.notCalled));373}));374375test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations during extension development', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {376instantiationService.stub(IEnvironmentService, { extensionDevelopmentLocationURI: [URI.file('/folder/file')], isExtensionDevelopment: true });377return testNoPromptOrRecommendationsForValidRecommendations(mockTestData.validRecommendedExtensions);378}));379380test('ExtensionRecommendationsService: No workspace recommendations or prompts when extensions.json has empty array', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {381return testNoPromptForValidRecommendations([]);382}));383384test('ExtensionRecommendationsService: Prompt for valid workspace recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {385await setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions);386testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));387388await Event.toPromise(promptedEmitter.event);389const recommendations = Object.keys(testObject.getAllRecommendationsWithReason());390const expected = [...mockTestData.validRecommendedExtensions, 'unknown.extension'];391assert.strictEqual(recommendations.length, expected.length);392expected.forEach(x => {393assert.strictEqual(recommendations.indexOf(x.toLowerCase()) > -1, true);394});395}));396397test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {398instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);399return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);400}));401402test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {403instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal);404return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions.map(x => x.toUpperCase()));405}));406407test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {408testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true });409return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);410}));411412test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if showRecommendationsOnlyOnDemand is set', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {413testConfigurationService.setUserConfiguration(ConfigurationKey, { showRecommendationsOnlyOnDemand: true });414return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {415testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));416return testObject.activationPromise.then(() => {417assert.ok(!prompted);418});419});420}));421422test('ExtensionRecommendationsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {423instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);424return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions);425}));426427test('ExtensionRecommendationsService: No Recommendations of globally ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {428instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);429instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]', StorageScope.PROFILE, StorageTarget.MACHINE);430instantiationService.get(IStorageService).store('extensionsAssistant/ignored_recommendations', '["ms-dotnettools.csharp", "mockpublisher2.mockextension2"]', StorageScope.PROFILE, StorageTarget.MACHINE);431432return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions).then(() => {433testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));434return testObject.activationPromise.then(() => {435const recommendations = testObject.getAllRecommendationsWithReason();436assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been globally ignored437assert.ok(recommendations['ms-python.python']); // stored recommendation438assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation439assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been globally ignored440});441});442}));443444test('ExtensionRecommendationsService: No Recommendations of workspace ignored recommendations', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {445const ignoredRecommendations = ['ms-dotnettools.csharp', 'mockpublisher2.mockextension2']; // ignore a stored recommendation and a workspace recommendation.446const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';447instantiationService.get(IStorageService).store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);448instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);449450return setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, ignoredRecommendations).then(() => {451testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));452return testObject.activationPromise.then(() => {453const recommendations = testObject.getAllRecommendationsWithReason();454assert.ok(!recommendations['ms-dotnettools.csharp']); // stored recommendation that has been workspace ignored455assert.ok(recommendations['ms-python.python']); // stored recommendation456assert.ok(recommendations['mockpublisher1.mockextension1']); // workspace recommendation457assert.ok(!recommendations['mockpublisher2.mockextension2']); // workspace recommendation that has been workspace ignored458});459});460}));461462test('ExtensionRecommendationsService: Able to retrieve collection of all ignored recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {463464const storageService = instantiationService.get(IStorageService);465const workspaceIgnoredRecommendations = ['ms-dotnettools.csharp']; // ignore a stored recommendation and a workspace recommendation.466const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';467const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.468storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);469storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);470storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);471472await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions, workspaceIgnoredRecommendations);473testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));474await testObject.activationPromise;475476const recommendations = testObject.getAllRecommendationsWithReason();477assert.deepStrictEqual(Object.keys(recommendations), ['ms-python.python', 'mockpublisher1.mockextension1']);478}));479480test('ExtensionRecommendationsService: Able to dynamically ignore/unignore global recommendations', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {481const storageService = instantiationService.get(IStorageService);482483const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python"]';484const globallyIgnoredRecommendations = '["mockpublisher2.mockextension2"]'; // ignore a workspace recommendation.485storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);486storageService.store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);487storageService.store('extensionsAssistant/ignored_recommendations', globallyIgnoredRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);488489await setUpFolderWorkspace('myFolder', mockTestData.validRecommendedExtensions);490const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);491testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));492await testObject.activationPromise;493494let recommendations = testObject.getAllRecommendationsWithReason();495assert.ok(recommendations['ms-python.python']);496assert.ok(recommendations['mockpublisher1.mockextension1']);497assert.ok(!recommendations['mockpublisher2.mockextension2']);498499extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', true);500501recommendations = testObject.getAllRecommendationsWithReason();502assert.ok(recommendations['ms-python.python']);503assert.ok(!recommendations['mockpublisher1.mockextension1']);504assert.ok(!recommendations['mockpublisher2.mockextension2']);505506extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation('mockpublisher1.mockextension1', false);507508recommendations = testObject.getAllRecommendationsWithReason();509assert.ok(recommendations['ms-python.python']);510assert.ok(recommendations['mockpublisher1.mockextension1']);511assert.ok(!recommendations['mockpublisher2.mockextension2']);512}));513514test('test global extensions are modified and recommendation change event is fired when an extension is ignored', async () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {515const storageService = instantiationService.get(IStorageService);516const changeHandlerTarget = sinon.spy();517const ignoredExtensionId = 'Some.Extension';518519storageService.store('extensionsAssistant/workspaceRecommendationsIgnore', true, StorageScope.WORKSPACE, StorageTarget.MACHINE);520storageService.store('extensionsAssistant/ignored_recommendations', '["ms-vscode.vscode"]', StorageScope.PROFILE, StorageTarget.MACHINE);521522await setUpFolderWorkspace('myFolder', []);523testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));524const extensionIgnoredRecommendationsService = instantiationService.get(IExtensionIgnoredRecommendationsService);525disposableStore.add(extensionIgnoredRecommendationsService.onDidChangeGlobalIgnoredRecommendation(changeHandlerTarget));526extensionIgnoredRecommendationsService.toggleGlobalIgnoredRecommendation(ignoredExtensionId, true);527await testObject.activationPromise;528529assert.ok(changeHandlerTarget.calledOnce);530assert.ok(changeHandlerTarget.getCall(0).calledWithMatch({ extensionId: ignoredExtensionId.toLowerCase(), isRecommended: false }));531}));532533test('ExtensionRecommendationsService: Get file based recommendations from storage (old format)', () => runWithFakedTimers<void>({ useFakeTimers: true }, async () => {534const storedRecommendations = '["ms-dotnettools.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]';535instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);536537return setUpFolderWorkspace('myFolder', []).then(() => {538testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));539return testObject.activationPromise.then(() => {540const recommendations = testObject.getFileBasedRecommendations();541assert.strictEqual(recommendations.length, 2);542assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips543assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips544assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips545});546});547}));548549test('ExtensionRecommendationsService: Get file based recommendations from storage (new format)', async () => {550const milliSecondsInADay = 1000 * 60 * 60 * 24;551const now = Date.now();552const tenDaysOld = 10 * milliSecondsInADay;553const storedRecommendations = `{"ms-dotnettools.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`;554instantiationService.get(IStorageService).store('extensionsAssistant/recommendations', storedRecommendations, StorageScope.PROFILE, StorageTarget.MACHINE);555556await setUpFolderWorkspace('myFolder', []);557testObject = disposableStore.add(instantiationService.createInstance(ExtensionRecommendationsService));558await testObject.activationPromise;559560const recommendations = testObject.getFileBasedRecommendations();561assert.strictEqual(recommendations.length, 2);562assert.ok(recommendations.some(extensionId => extensionId === 'ms-dotnettools.csharp')); // stored recommendation that exists in product.extensionTips563assert.ok(recommendations.some(extensionId => extensionId === 'ms-python.python')); // stored recommendation that exists in product.extensionImportantTips564assert.ok(recommendations.every(extensionId => extensionId !== 'ms-vscode.vscode-typescript-tslint-plugin')); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips565assert.ok(recommendations.every(extensionId => extensionId !== 'lukehoban.Go')); //stored recommendation that is older than a week566});567});568569570