diff --git a/src/azure/ApiCenter/ApiCenterRestAPIs.ts b/src/azure/ApiCenter/ApiCenterRestAPIs.ts index 503c6fa..1bebb8e 100644 --- a/src/azure/ApiCenter/ApiCenterRestAPIs.ts +++ b/src/azure/ApiCenter/ApiCenterRestAPIs.ts @@ -7,18 +7,18 @@ export const APICenterRestAPIs = { GetAPIVersions: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions?api-version=${restApiVersion}`, GetAPIDeployments: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/deployments?api-version=${restApiVersion}`, GetAPIDefinition: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions?api-version=${restApiVersion}`, - GetRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral?api-version=${restApiVersion}`, + GetRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi?api-version=${restApiVersion}`, CreateAPI: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}?api-version=${restApiVersion}`, CreateAPIVersion: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}?api-version=${restApiVersion}`, CreateAPIDeployment: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, deploymentName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/deployments/${deploymentName}?api-version=${restApiVersion}`, CreateAPIDefinition: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}?api-version=${restApiVersion}`, - CreateRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral?api-version=${restApiVersion}`, + CreateRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi?api-version=${restApiVersion}`, ImportAPISpecification: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}/importSpecification?api-version=${restApiVersion}`, ExportApiSpecification: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}/exportSpecification?api-version=${restApiVersion}`, - ImportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/importRuleset?api-version=${restApiVersion}`, - ExportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/exportRuleset?api-version=${restApiVersion}`, + ImportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi/importRuleset?api-version=${restApiVersion}`, + ExportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi/exportRuleset?api-version=${restApiVersion}`, }; diff --git a/src/azure/ApiCenter/ApiCenterService.ts b/src/azure/ApiCenter/ApiCenterService.ts index 82662ad..ca2d031 100644 --- a/src/azure/ApiCenter/ApiCenterService.ts +++ b/src/azure/ApiCenter/ApiCenterService.ts @@ -4,14 +4,14 @@ import { HttpOperationResponse, RequestPrepareOptions, ServiceClient } from "@az import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { getCredentialForToken } from "../../utils/credentialUtil"; import { APICenterRestAPIs } from "./ApiCenterRestAPIs"; -import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport } from "./contracts"; +import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport, ApiCenterRulesetImportResult, ApiCenterRulesetImportStatus, ArmAsyncOperationStatus } from "./contracts"; export class ApiCenterService { private susbcriptionContext: ISubscriptionContext; private resourceGroupName: string; private apiCenterName: string; private apiVersion: string = "2023-07-01-preview"; - private apiVersionPreview: string = "2024-03-15-preview"; + private apiVersionNew: string = "2024-03-01"; constructor(susbcriptionContext: ISubscriptionContext, resourceGroupName: string, apiCenterName: string) { this.susbcriptionContext = susbcriptionContext; this.apiCenterName = apiCenterName; @@ -93,7 +93,7 @@ export class ApiCenterService { const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "GET", - url: APICenterRestAPIs.GetRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview) + url: APICenterRestAPIs.GetRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew) }; const response = await client.sendRequest(options); return response; @@ -170,7 +170,7 @@ export class ApiCenterService { const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "PUT", - url: APICenterRestAPIs.CreateRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview), + url: APICenterRestAPIs.CreateRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew), body: { properties: apiCenterRulesetConfig.properties } @@ -226,16 +226,44 @@ export class ApiCenterService { return response.parsedBody; } - public async importRuleset(importPayload: ApiCenterRulesetImport): Promise { + public async importRuleset(importPayload: ApiCenterRulesetImport): Promise { const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken()); const client = new ServiceClient(creds); - const options: RequestPrepareOptions = { + let options: RequestPrepareOptions = { method: "POST", - url: APICenterRestAPIs.ImportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview), + url: APICenterRestAPIs.ImportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew), body: importPayload }; - const response = await client.sendRequest(options); - return response; + let response = await client.sendRequest(options); + + if (response.status === 202) { + const location = response.headers.get("Location"); + + if (!location) { + return { isSuccessful: false }; + } + + options = { + method: "GET", + url: location, + }; + + const timeout = 60000; // 1 minute in milliseconds + let startTime = Date.now(); + do { + response = await client.sendRequest(options); + const responseBody: ApiCenterRulesetImportStatus = response.parsedBody; + if (responseBody?.status === ArmAsyncOperationStatus.Succeeded) { + return { isSuccessful: true }; + } + if (responseBody?.status === ArmAsyncOperationStatus.Failed) { + return { isSuccessful: false, message: responseBody.properties?.comment }; + } + } while (Date.now() - startTime < timeout); + return { isSuccessful: false }; + } else { + return { isSuccessful: false, message: response.bodyAsText }; + } } public async exportRuleset(): Promise { @@ -243,7 +271,7 @@ export class ApiCenterService { const client = new ServiceClient(creds); const options: RequestPrepareOptions = { method: "POST", - url: APICenterRestAPIs.ExportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview) + url: APICenterRestAPIs.ExportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew) }; const response = await client.sendRequest(options); return response.parsedBody; diff --git a/src/azure/ApiCenter/contracts.ts b/src/azure/ApiCenter/contracts.ts index 0b5563d..28120d7 100644 --- a/src/azure/ApiCenter/contracts.ts +++ b/src/azure/ApiCenter/contracts.ts @@ -96,6 +96,20 @@ export type ApiCenterRulesetImport = { value: string; }; +export type ApiCenterRulesetImportStatus = { + id: string; + name: string; + status: ArmAsyncOperationStatus; + properties: { + comment?: string; + }; +}; + +export type ApiCenterRulesetImportResult = { + isSuccessful: boolean; + message?: string | null; +}; + export type ApiCenterRulesetExport = { format: string; value: string; @@ -135,3 +149,15 @@ export enum ApiSpecExportResultFormat { inline = 'inline', link = 'link', }; + +export enum ArmAsyncOperationStatus { + NotStarted = 'NotStarted', + InProgress = 'InProgress', + Succeeded = 'Succeeded', + Failed = 'Failed', + Canceled = 'Canceled', +} + +export enum ApiCenterRulesetImportFormat { + InlineZip = 'inline-zip', +}; diff --git a/src/commands/rules/deployRules.ts b/src/commands/rules/deployRules.ts index a213eee..a687b06 100644 --- a/src/commands/rules/deployRules.ts +++ b/src/commands/rules/deployRules.ts @@ -4,7 +4,7 @@ import { getResourceGroupFromId } from "@microsoft/vscode-azext-azureutils"; import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterRulesetImport } from "../../azure/ApiCenter/contracts"; +import { ApiCenterRulesetImport, ApiCenterRulesetImportFormat } from "../../azure/ApiCenter/contracts"; import { RulesTreeItem } from "../../tree/rules/RulesTreeItem"; import { UiStrings } from "../../uiStrings"; import { hasFiles } from "../../utils/fsUtil"; @@ -18,20 +18,25 @@ export async function deployRules(context: IActionContext, node: RulesTreeItem) return; } - const content = (await zipFolderToBuffer(rulesFolderPath)).toString('base64'); + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: UiStrings.DeployRules + }, async (progress, token) => { + const content = (await zipFolderToBuffer(rulesFolderPath)).toString('base64'); - const resourceGroupName = getResourceGroupFromId(node.apiCenter.id); - const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name); + const resourceGroupName = getResourceGroupFromId(node.apiCenter.id); + const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name); - const importPayload: ApiCenterRulesetImport = { - value: content, - format: "InlineZip", - }; - const response = await apiCenterService.importRuleset(importPayload); + const importPayload: ApiCenterRulesetImport = { + value: content, + format: ApiCenterRulesetImportFormat.InlineZip, + }; + const response = await apiCenterService.importRuleset(importPayload); - if (response.status === 200) { - vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name)); - } else { - vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToDeployRules, response.bodyAsText ?? `status code ${response.status}`)); - } + if (response.isSuccessful) { + vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name)); + } else { + throw new Error(vscode.l10n.t(UiStrings.FailedToDeployRules, response.message ? `Error: ${response.message}` : "")); + } + }); } diff --git a/src/commands/rules/enableRules.ts b/src/commands/rules/enableRules.ts index 39e2a55..730cb9d 100644 --- a/src/commands/rules/enableRules.ts +++ b/src/commands/rules/enableRules.ts @@ -5,7 +5,7 @@ import { IActionContext } from "@microsoft/vscode-azext-utils"; import * as path from 'path'; import * as vscode from 'vscode'; import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService"; -import { ApiCenterRulesetConfig, ApiCenterRulesetImport } from "../../azure/ApiCenter/contracts"; +import { ApiCenterRulesetConfig, ApiCenterRulesetImport, ApiCenterRulesetImportFormat } from "../../azure/ApiCenter/contracts"; import { ext } from "../../extensionVariables"; import { RulesTreeItem } from "../../tree/rules/RulesTreeItem"; import { UiStrings } from "../../uiStrings"; @@ -13,42 +13,42 @@ import { zipFolderToBuffer } from "../../utils/zipUtils"; const apiCenterRulesetConfig: ApiCenterRulesetConfig = { properties: { - analyzerVersion: "1.0.0", - apiType: "rest", - lifecycleStage: "testing", + analyzerType: "spectral" } }; export async function enableRules(context: IActionContext, node: RulesTreeItem) { - const resourceGroupName = getResourceGroupFromId(node.apiCenter.id); - const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name); - - let response = await apiCenterService.createOrUpdateApiCenterRulesetConfig(apiCenterRulesetConfig); - - if (response.status === 200) { - vscode.window.showInformationMessage((vscode.l10n.t(UiStrings.RulesEnabled, node.apiCenter.name))); - } else { - vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToEnableRules, response.bodyAsText ?? `status code ${response.status}`)); - return; - } - - // Temporary workaround to deploy default rules after enabling rules - // In future, default rules need to be generated in control plane - const defaultRulesFolderPath = path.join(ext.context.extensionPath, 'templates', 'rules', 'default-ruleset'); - - const content = (await zipFolderToBuffer(defaultRulesFolderPath)).toString('base64'); - - const importPayload: ApiCenterRulesetImport = { - value: content, - format: "InlineZip", - }; - response = await apiCenterService.importRuleset(importPayload); - - if (response.status === 200) { - vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name)); - node.updateStatusToEnable(); - await node.refresh(context); - } else { - vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToDeployRules, response.bodyAsText ?? `status code ${response.status}`)); - } + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: UiStrings.EnableRules + }, async (progress, token) => { + const resourceGroupName = getResourceGroupFromId(node.apiCenter.id); + const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name); + + const response = await apiCenterService.createOrUpdateApiCenterRulesetConfig(apiCenterRulesetConfig); + + if (response.status !== 200) { + throw new Error(vscode.l10n.t(UiStrings.FailedToEnableRules, response.bodyAsText ? `Error: ${response.bodyAsText}` : `Status Code: ${response.status}`)); + } + + // Temporary workaround to deploy default rules after enabling rules + // In future, default rules need to be generated in control plane + const defaultRulesFolderPath = path.join(ext.context.extensionPath, 'templates', 'rules', 'default-ruleset'); + + const content = (await zipFolderToBuffer(defaultRulesFolderPath)).toString('base64'); + + const importPayload: ApiCenterRulesetImport = { + value: content, + format: ApiCenterRulesetImportFormat.InlineZip, + }; + const importRulesetResponse = await apiCenterService.importRuleset(importPayload); + + if (importRulesetResponse.isSuccessful) { + vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesEnabled, node.apiCenter.name)); + node.updateStatusToEnable(); + await node.refresh(context); + } else { + throw new Error(vscode.l10n.t(UiStrings.FailedToEnableRules, importRulesetResponse.message ? `Error: ${importRulesetResponse.message}` : "")); + } + }); } diff --git a/src/test/unit/azure/ApiCenter/ApiCenterService.test.ts b/src/test/unit/azure/ApiCenter/ApiCenterService.test.ts new file mode 100644 index 0000000..2e0670f --- /dev/null +++ b/src/test/unit/azure/ApiCenter/ApiCenterService.test.ts @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import { HttpHeaders, HttpOperationResponse, ServiceClient } from "@azure/ms-rest-js"; +import { ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import * as assert from "assert"; +import * as sinon from "sinon"; +import { ApiCenterService } from "../../../../azure/ApiCenter/ApiCenterService"; +import { ApiCenterRulesetImport, ApiCenterRulesetImportFormat } from "../../../../azure/ApiCenter/contracts"; + +describe("ApiCenterService", () => { + let sandbox: sinon.SinonSandbox; + let subscriptionContext: ISubscriptionContext; + before(() => { + sandbox = sinon.createSandbox(); + }); + beforeEach(() => { + subscriptionContext = { + credentials: { + getToken: sandbox.stub().resolves({ token: 'fake-token' }) + } + } as unknown as ISubscriptionContext; + }); + afterEach(() => { + sandbox.restore(); + }); + it("importRuleset succeeded", async () => { + const mockResponse1 = { status: 202 } as HttpOperationResponse; + mockResponse1.headers = new HttpHeaders({ Location: "fakeLocation" }); + const mockResponse2 = { + status: 200, + parsedBody: { status: "Succeeded" }, + } as HttpOperationResponse; + const sendRequestStub = sandbox.stub(ServiceClient.prototype, "sendRequest"); + sendRequestStub.onFirstCall().resolves(mockResponse1); + sendRequestStub.onSecondCall().resolves(mockResponse2); + + const apiCenterService = new ApiCenterService(subscriptionContext, "fakeResourceGroup", "fakeServiceName"); + const importPayload: ApiCenterRulesetImport = { + value: "fakeValue", + format: ApiCenterRulesetImportFormat.InlineZip, + }; + + const response = await apiCenterService.importRuleset(importPayload); + + assert.strictEqual(response.isSuccessful, true); + }); + it("importRuleset failed on first call", async () => { + sandbox.stub(ServiceClient.prototype, "sendRequest").resolves({ status: 500, bodyAsText: "error" } as HttpOperationResponse); + + const apiCenterService = new ApiCenterService(subscriptionContext, "fakeResourceGroup", "fakeServiceName"); + const importPayload: ApiCenterRulesetImport = { + value: "fakeValue", + format: ApiCenterRulesetImportFormat.InlineZip, + }; + + const response = await apiCenterService.importRuleset(importPayload); + + assert.strictEqual(response.isSuccessful, false); + assert.strictEqual(response.message, "error"); + }); + it("importRuleset failed on status check", async () => { + const mockResponse1 = { status: 202 } as HttpOperationResponse; + mockResponse1.headers = new HttpHeaders({ Location: "fakeLocation" }); + const mockResponse2 = { + status: 202, + parsedBody: { status: "InProgress" }, + } as HttpOperationResponse; + const mockResponse3 = { + status: 400, + parsedBody: { + status: "Failed", + properties: { + comment: "error" + } + }, + } as HttpOperationResponse; + const sendRequestStub = sandbox.stub(ServiceClient.prototype, "sendRequest"); + sendRequestStub.onFirstCall().resolves(mockResponse1); + sendRequestStub.onSecondCall().resolves(mockResponse2); + sendRequestStub.onThirdCall().resolves(mockResponse3); + + const apiCenterService = new ApiCenterService(subscriptionContext, "fakeResourceGroup", "fakeServiceName"); + const importPayload: ApiCenterRulesetImport = { + value: "fakeValue", + format: ApiCenterRulesetImportFormat.InlineZip, + }; + + const response = await apiCenterService.importRuleset(importPayload); + + assert.strictEqual(response.isSuccessful, false); + assert.strictEqual(response.message, "error"); + }); +}); diff --git a/src/test/unit/commands/rules/enableRules.test.ts b/src/test/unit/commands/rules/enableRules.test.ts index 6416661..67a1875 100644 --- a/src/test/unit/commands/rules/enableRules.test.ts +++ b/src/test/unit/commands/rules/enableRules.test.ts @@ -7,7 +7,7 @@ import * as path from 'path'; import * as sinon from "sinon"; import * as vscode from "vscode"; import { ApiCenterService } from "../../../../azure/ApiCenter/ApiCenterService"; -import { ApiCenter } from "../../../../azure/ApiCenter/contracts"; +import { ApiCenter, ApiCenterRulesetImportResult } from "../../../../azure/ApiCenter/contracts"; import { enableRules } from "../../../../commands/rules/enableRules"; import { RulesTreeItem } from "../../../../tree/rules/RulesTreeItem"; @@ -30,20 +30,41 @@ describe("enableRules", () => { afterEach(() => { sandbox.restore(); }); - it('enable rules with status code 200', async () => { + it('enabling rules succeeded', async () => { sandbox.stub(path, 'join').returns(__dirname); const showInformationMessage = sandbox.spy(vscode.window, "showInformationMessage"); sandbox.stub(ApiCenterService.prototype, "createOrUpdateApiCenterRulesetConfig").resolves({ status: 200 } as HttpOperationResponse); - sandbox.stub(ApiCenterService.prototype, "importRuleset").resolves({ status: 200 } as HttpOperationResponse); + sandbox.stub(ApiCenterService.prototype, "importRuleset").resolves({ isSuccessful: true } as ApiCenterRulesetImportResult); await enableRules({} as IActionContext, node); - sandbox.assert.calledTwice(showInformationMessage); + sandbox.assert.calledOnce(showInformationMessage); assert.ok(node.isEnabled); }); - it('enable rules with no status code 200', async () => { - const showErrorMessage = sandbox.spy(vscode.window, "showErrorMessage"); - sandbox.stub(ApiCenterService.prototype, "createOrUpdateApiCenterRulesetConfig").resolves({ status: 400 } as HttpOperationResponse); - await enableRules({} as IActionContext, node); - sandbox.assert.calledOnce(showErrorMessage); + it('enabling rules failed when creating ruleset config', async () => { + sandbox.stub(ApiCenterService.prototype, "createOrUpdateApiCenterRulesetConfig").resolves({ status: 400, bodyAsText: 'error' } as HttpOperationResponse); + + await assert.rejects( + async () => { + await enableRules({} as IActionContext, node); + }, + { + message: "Failed to enable API Analysis. Error: error", + } + ); + assert.ok(!node.isEnabled); + }); + it('enabling rules failed when importing ruleset', async () => { + sandbox.stub(path, 'join').returns(__dirname); + sandbox.stub(ApiCenterService.prototype, "createOrUpdateApiCenterRulesetConfig").resolves({ status: 200 } as HttpOperationResponse); + sandbox.stub(ApiCenterService.prototype, "importRuleset").resolves({ isSuccessful: false, message: 'error' } as ApiCenterRulesetImportResult); + + await assert.rejects( + async () => { + await enableRules({} as IActionContext, node); + }, + { + message: "Failed to enable API Analysis. Error: error", + } + ); assert.ok(!node.isEnabled); }); }); diff --git a/src/uiStrings.ts b/src/uiStrings.ts index df19fdb..db18ecf 100644 --- a/src/uiStrings.ts +++ b/src/uiStrings.ts @@ -106,10 +106,12 @@ export class UiStrings { static readonly DeleteCustomFunction = vscode.l10n.t("Are you sure you want to delete '{0}'?"); static readonly FileAlreadyExists = vscode.l10n.t("The file '{0}' already exists. Please input a different name."); static readonly NoRulesFolder = vscode.l10n.t("Rules folder '{0}' is empty. No files to deploy."); + static readonly DeployRules = vscode.l10n.t("Deploying Rules..."); static readonly RulesDeployed = vscode.l10n.t("Rules deployed to '{0}'."); - static readonly FailedToDeployRules = vscode.l10n.t("Failed to deploy rules. Error: {0}"); + static readonly FailedToDeployRules = vscode.l10n.t("Failed to deploy rules. {0}"); + static readonly EnableRules = vscode.l10n.t("Enabling API Analysis..."); static readonly RulesEnabled = vscode.l10n.t("API Analysis enabled for '{0}'."); - static readonly FailedToEnableRules = vscode.l10n.t("Failed to enable API Analysis. Error: {0}"); + static readonly FailedToEnableRules = vscode.l10n.t("Failed to enable API Analysis. {0}"); static readonly RulesFolderNotEmpty = vscode.l10n.t("The rules folder '{0}' is not empty. Do you want to overwrite the existing files?"); static readonly Yes = vscode.l10n.t("Yes"); static readonly No = vscode.l10n.t("No");