Skip to content

Commit

Permalink
Merge pull request #2015 from zowe/feat/plugins/schema-updates
Browse files Browse the repository at this point in the history
feat(plugins): add/remove from schema during `zowe plugins install/uninstall`
  • Loading branch information
zFernand0 authored Jan 19, 2024
2 parents 4168971 + e2b026d commit 632c20b
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 13 deletions.
3 changes: 2 additions & 1 deletion packages/imperative/src/config/src/ConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ export class ConfigSchema {
* Transform a JSON schema to an Imperative profile schema.
* @param schema The JSON schema for profile properties
* @returns Imperative profile schema
* @internal
*/
private static parseSchema(schema: any): IProfileSchema {
public static parseSchema(schema: any): IProfileSchema {
const properties: { [key: string]: IProfileProperty } = {};
for (const [k, v] of Object.entries((schema.properties.properties || {}) as { [key: string]: any })) {
properties[k] = { type: v.type };
Expand Down
9 changes: 8 additions & 1 deletion packages/imperative/src/config/src/__mocks__/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*
*/

import { IConfigOpts } from "../..";
import { IConfigOpts, IConfigSchemaInfo } from "../..";
import { IConfigLayer } from "../../src/doc/IConfigLayer";

export class Config {
Expand Down Expand Up @@ -54,4 +54,11 @@ export class Config {
return config;
}

public getSchemaInfo(): IConfigSchemaInfo {
return {
local: true,
resolved: "/some/path/to/schema.json",
original: "/some/path/to/schema.json"
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/

import { IProfileTypeConfiguration } from "../../../..";

const mockTypeConfig: IProfileTypeConfiguration = {
type: "test-type",
schema: {
title: "test-type",
description: "A test type profile",
type: "object",
required: [],
properties: {
host: {
type: "string",
secure: false
}
}
}
};

export default mockTypeConfig;
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ import { ConfigurationLoader } from "../../../../src/ConfigurationLoader";
import { UpdateImpConfig } from "../../../../src/UpdateImpConfig";
import * as fs from "fs";
import * as path from "path";

import { gt as versionGreaterThan } from "semver";
import { ProfileInfo } from "../../../../../config";
import mockTypeConfig from "../../__resources__/typeConfiguration";
import { updateExtendersJson } from "../../../../src/plugins/utilities/npm-interface/install";
import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts";

function setResolve(toResolve: string, resolveTo?: string) {
expectedVal = toResolve;
Expand All @@ -78,7 +82,12 @@ describe("PMF: Install Interface", () => {
PMF_requirePluginModuleCallback: pmfI.requirePluginModuleCallback as Mock<typeof pmfI.requirePluginModuleCallback>,
ConfigurationLoader_load: ConfigurationLoader.load as Mock<typeof ConfigurationLoader.load>,
UpdateImpConfig_addProfiles: UpdateImpConfig.addProfiles as Mock<typeof UpdateImpConfig.addProfiles>,
path: path as unknown as Mock<typeof path>
path: path as unknown as Mock<typeof path>,
ConfigSchema_loadSchema: jest.spyOn(ConfigSchema, "loadSchema"),
ProfileInfo: {
readExtendersJsonFromDisk: jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk"),
writeExtendersJson: jest.spyOn(ProfileInfo, "writeExtendersJson")
}
};

const packageName = "a";
Expand All @@ -101,7 +110,16 @@ describe("PMF: Install Interface", () => {
mocks.sync.mockReturnValue("fake_find-up_sync_result" as any);
jest.spyOn(path, "dirname").mockReturnValue("fake-dirname");
jest.spyOn(path, "join").mockReturnValue("/fake/join/path");
mocks.ConfigurationLoader_load.mockReturnValue({ profiles: ["fake"] } as any);
mocks.ProfileInfo.readExtendersJsonFromDisk.mockReturnValue({
profileTypes: {
"zosmf": {
from: ["Zowe CLI"]
}
}
});
mocks.ProfileInfo.writeExtendersJson.mockImplementation();
mocks.ConfigSchema_loadSchema.mockReturnValue([mockTypeConfig]);
mocks.ConfigurationLoader_load.mockReturnValue({ profiles: [mockTypeConfig] } as any);
});

afterAll(() => {
Expand Down Expand Up @@ -130,7 +148,7 @@ describe("PMF: Install Interface", () => {
if (shouldUpdate) {
expect(mocks.UpdateImpConfig_addProfiles).toHaveBeenCalledTimes(1);
expect(mocks.ConfigSchema_updateSchema).toHaveBeenCalledTimes(1);
expect(mocks.ConfigSchema_updateSchema).toHaveBeenCalledWith({ layer: "global" });
expect(mocks.ConfigSchema_updateSchema).toHaveBeenCalledWith(expect.objectContaining({ layer: "global" }));
} else {
expect(mocks.UpdateImpConfig_addProfiles).not.toHaveBeenCalled();
expect(mocks.ConfigSchema_updateSchema).not.toHaveBeenCalled();
Expand Down Expand Up @@ -165,7 +183,7 @@ describe("PMF: Install Interface", () => {
describe("Basic install", () => {
beforeEach(() => {
mocks.getPackageInfo.mockResolvedValue({ name: packageName, version: packageVersion } as never);
jest.spyOn(fs, "existsSync").mockReturnValueOnce(true);
jest.spyOn(fs, "existsSync").mockReturnValue(true);
jest.spyOn(path, "normalize").mockReturnValue("testing");
jest.spyOn(fs, "lstatSync").mockReturnValue({
isSymbolicLink: jest.fn().mockReturnValue(true)
Expand Down Expand Up @@ -326,7 +344,7 @@ describe("PMF: Install Interface", () => {
});
});

it("should merge contents of previous json file", async () => {
it("should merge contents of previous plugins.json file", async () => {
// value for our previous plugins.json
const oneOldPlugin: IPluginJson = {
plugin1: {
Expand Down Expand Up @@ -355,6 +373,122 @@ describe("PMF: Install Interface", () => {
});
});

describe("Updating the global schema", () => {
const expectTestSchemaMgmt = async (opts: {
schemaExists: boolean;
newProfileType: boolean;
version?: string;
lastVersion?: string;
}) => {
const oneOldPlugin: IPluginJson = {
plugin1: {
package: "plugin1",
registry: packageRegistry,
version: "1.2.3"
}
};
if (opts.newProfileType) {
const schema = { ...mockTypeConfig, schema: { ...mockTypeConfig.schema, version: opts.version } };
mocks.ConfigurationLoader_load.mockReturnValue({
profiles: [
schema
]
} as any);
}

mocks.getPackageInfo.mockResolvedValue({ name: packageName, version: packageVersion } as never);
jest.spyOn(fs, "existsSync").mockReturnValueOnce(true).mockReturnValueOnce(opts.schemaExists);
jest.spyOn(path, "normalize").mockReturnValue("testing");
jest.spyOn(fs, "lstatSync").mockReturnValue({
isSymbolicLink: jest.fn().mockReturnValue(true)
} as any);
mocks.readFileSync.mockReturnValue(oneOldPlugin as any);

if (opts.lastVersion) {
mocks.ProfileInfo.readExtendersJsonFromDisk.mockReturnValueOnce({
profileTypes: {
"test-type": {
from: [oneOldPlugin.plugin1.package],
version: opts.lastVersion,
latestFrom: oneOldPlugin.plugin1.package
}
}
});
}

setResolve(packageName);
await install(packageName, packageRegistry);
if (opts.schemaExists) {
expect(mocks.ConfigSchema_updateSchema).toHaveBeenCalled();
} else {
expect(mocks.ConfigSchema_updateSchema).not.toHaveBeenCalled();
}

if (opts.version && opts.lastVersion) {
if (versionGreaterThan(opts.version, opts.lastVersion)) {
expect(mocks.ProfileInfo.writeExtendersJson).toHaveBeenCalled();
} else {
expect(mocks.ProfileInfo.writeExtendersJson).not.toHaveBeenCalled();
}
}
};
it("should update the schema to contain the new profile type", async () => {
expectTestSchemaMgmt({
schemaExists: true,
newProfileType: true
});
});

it("should not update the schema if it doesn't exist", async () => {
expectTestSchemaMgmt({
schemaExists: false,
newProfileType: true
});
});

it("updates the schema with a newer schema version than the one present", () => {
expectTestSchemaMgmt({
schemaExists: true,
newProfileType: true,
version: "2.0.0",
lastVersion: "1.0.0"
});
});

it("doesn't update the schema with an older schema version than the one present", () => {
expectTestSchemaMgmt({
schemaExists: true,
newProfileType: true,
version: "1.0.0",
lastVersion: "2.0.0"
});
});
});

describe("updating extenders.json", () => {
it("adds a new profile type if it doesn't exist", () => {
const extendersJson = { profileTypes: {} } as IExtendersJsonOpts;
updateExtendersJson(extendersJson, { name: "aPkg", version: "1.0.0" }, mockTypeConfig);
expect(extendersJson.profileTypes["test-type"]).not.toBeUndefined();
});

it("replaces a profile type with a newer schema version", () => {
const extendersJson = { profileTypes: { "test-type": { from: ["Zowe Client App"], version: "0.9.0" } } };
updateExtendersJson(extendersJson, { name: "aPkg", version: "1.0.0" },
{ ...mockTypeConfig, schema: { ...mockTypeConfig.schema, version: "1.0.0" } });
expect(extendersJson.profileTypes["test-type"]).not.toBeUndefined();
expect(extendersJson.profileTypes["test-type"].version).toBe("1.0.0");
});

it("does not change the schema version if older", () => {
const extendersJson = { profileTypes: { "test-type": { from: ["Zowe Client App"], version: "1.2.0" } } };
updateExtendersJson(extendersJson, { name: "aPkg", version: "1.0.0" },
{ ...mockTypeConfig, schema: { ...mockTypeConfig.schema, version: "1.0.0" } });
expect(extendersJson.profileTypes["test-type"]).not.toBeUndefined();
expect(extendersJson.profileTypes["test-type"].version).toBe("1.2.0");
});
});

it("should throw errors", async () => {
// Create a placeholder error object that should be set after the call to install
let expectedError: ImperativeError = new ImperativeError({
Expand Down
Loading

0 comments on commit 632c20b

Please sign in to comment.