From fa8642d9ab54854a1e495283ff9978ad4118aef8 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 29 Apr 2024 16:02:10 -0400 Subject: [PATCH 01/72] changes to include custom events Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEvent.ts | 41 +++- .../events/src/ImperativeEventConstants.ts | 21 +- .../src/events/src/ImperativeEventEmitter.ts | 192 ++++++++---------- .../events/src/doc/IImperativeEventJson.ts | 12 +- .../events/src/doc/IImperativeEventParms.ts | 12 +- 5 files changed, 158 insertions(+), 120 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index d8417295ed..6efba489eb 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -11,7 +11,7 @@ import { randomUUID } from "crypto"; import { IImperativeEventJson, IImperativeEventParms } from "./doc"; -import { ImperativeEventType } from "./ImperativeEventConstants"; +import { ImperativeEventTypes } from "./ImperativeEventConstants"; /** * @@ -43,21 +43,37 @@ export class ImperativeEvent { */ private mEventTime: string; + /** + * The name of event that occurred + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mEventName: string; + /** * The type of event that occurred * @private * @type {string} * @memberof ImperativeEvent */ - private mEventType: ImperativeEventType | string; + private mEventType: ImperativeEventTypes; + + /** + * File path for the event + * @private + * @type {string} + * @memberof ImperativeEvent + */ + private mLoc: string; /** - * Indicator of user-specific (if true) or shared (if false) events + * Indicator of custom ( A.K.A. 'extender') shared events as opposed to standard user or shared events * @private * @type {boolean} * @memberof ImperativeEvent */ - private isUserEvent: boolean; + private isCustomShared: boolean; /** * toString overload to be called automatically on string concatenation @@ -74,10 +90,12 @@ export class ImperativeEvent { public toJson = (): IImperativeEventJson => { return { time: this.eventTime, + name: this.eventName, type: this.eventType, source: this.appName, id: this.eventId, - user: this.isUserEvent, + loc: this.loc, + isCustomShared: this.isCustomShared, }; }; @@ -86,7 +104,8 @@ export class ImperativeEvent { this.mEventID = randomUUID(); this.mAppID = parms.appName; this.mEventType = parms.eventType; - this.isUserEvent = parms.isUser; + this.mLoc = parms.loc; + this.isCustomShared = parms.isCustomShared; parms.logger.debug("ImperativeEvent: " + this); } @@ -94,7 +113,11 @@ export class ImperativeEvent { return this.mEventTime; } - public get eventType(): ImperativeEventType | string { + public get eventName(): string { + return this.mEventName; + } + + public get eventType(): ImperativeEventTypes { return this.mEventType; } @@ -102,6 +125,10 @@ export class ImperativeEvent { return this.mAppID; } + public get loc(): string { + return this.mLoc; + } + public get eventId() : string { return this.mEventID; } diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 0fd35d70af..904200de63 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -16,12 +16,31 @@ export enum ImperativeSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } -export type ImperativeEventType = ImperativeUserEvents | ImperativeSharedEvents; +export enum ImperativeCustomShared { + CUSTOM_SHARED_EVENT = "customSharedEvent" +} + +export enum ImperativeCustomUser { + CUSTOM_USER_EVENT = "customUserEvent", +} + +export type ImperativeEventTypes = + typeof ImperativeUserEvents | + typeof ImperativeSharedEvents | + typeof ImperativeCustomShared | + typeof ImperativeCustomUser; /** * TODO: * The following list of event types will only be implemented upon request * + * BRAINSTORMING - What is needed multiple times that we need to keep track of? + * - project name + * - event name + * - app name + * - shared event (boolean) + * + * how are we determining if global or project?? * Shared events: * Global: * - $ZOWE_CLI_HOME/.events/onConfigChanged diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 429cfd07be..21d1df887b 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -10,11 +10,16 @@ */ import * as fs from "fs"; -import { homedir } from "os"; import { join } from "path"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error/src/ImperativeError"; -import { ImperativeEventType, ImperativeUserEvents, ImperativeSharedEvents } from "./ImperativeEventConstants"; +import { + ImperativeEventTypes, + ImperativeUserEvents, + ImperativeSharedEvents, + ImperativeCustomShared, + ImperativeCustomUser +} from "./ImperativeEventConstants"; import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger/src/Logger"; import { LoggerManager } from "../../logger/src/LoggerManager"; @@ -22,14 +27,20 @@ import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEv import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { + static initialize(app: string, arg1: { logger: Logger; }) { + throw new Error("Method not implemented."); + } private static mInstance: ImperativeEventEmitter; - private static initialized = false; + private initialized = false; private subscriptions: Map; private eventTimes: Map; public appName: string; + public eventType: ImperativeEventTypes; + public loc: string; + public isCustomShared: boolean; public logger: Logger; - public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { + private initialize(appName?: string, options?: IImperativeEventEmitterOpts) { if (this.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); } @@ -42,20 +53,12 @@ export class ImperativeEventEmitter { ImperativeEventEmitter.instance.appName = appName; ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } - public static get instance(): ImperativeEventEmitter { - if (this.mInstance == null) { - this.mInstance = new ImperativeEventEmitter(); - this.mInstance.subscriptions = new Map(); - this.mInstance.eventTimes = new Map(); - } - return this.mInstance; - } /** * Check to see if the Imperative Event Emitter instance has been initialized */ private ensureClassInitialized() { - if (!ImperativeEventEmitter.initialized) { + if (!this.initialized) { throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); } } @@ -90,12 +93,18 @@ export class ImperativeEventEmitter { /** * Helper method to initialize the event - * @param eventType The type of event to initialize + * @param eventName The name of event to initialize * @returns The initialized ImperativeEvent */ - private initEvent(eventType: ImperativeEventType | string): ImperativeEvent { + private initEvent(eventName: string): ImperativeEvent { this.ensureClassInitialized(); - return new ImperativeEvent({ appName: this.appName, eventType, isUser: this.isUserEvent(eventType), logger: this.logger }); + return new ImperativeEvent({ + appName: this.appName, + eventType: this.getEventType(eventName, this.isCustomShared), + loc: this.getEventDir(eventName, this.eventType, this.appName), + isCustomShared: this.isCustomShared, + logger: this.logger + }); } /** @@ -105,7 +114,7 @@ export class ImperativeEventEmitter { * @internal We do not want developers writing events directly, they should use the `emit...` methods */ private writeEvent(location: string, event: ImperativeEvent) { - const eventPath = join(location, event.eventType); + const eventPath = join(location, (event.eventType).toString()); const eventJson = { ...event.toJson(), loc: location }; this.ensureEventsDirExists(location); @@ -114,140 +123,111 @@ export class ImperativeEventEmitter { /** * Helper method to create watchers based on event strings and list of callbacks - * @param eventType type of event to which we will create a watcher for + * @param eventName name of event to which we will create a watcher for * @param callbacks list of all callbacks for this watcher * @returns The FSWatcher instance created */ - private setupWatcher(eventType: string, callbacks: Function[] = []): fs.FSWatcher { - const dir = this.getEventDir(eventType); + private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { + const dir = this.getEventDir(eventName, this.eventType, this.appName); + this.loc = dir; this.ensureEventsDirExists(dir); //ensure .events exist - this.ensureEventFileExists(join(dir, eventType)); - const watcher = fs.watch(join(dir, eventType), (event: "rename" | "change") => { + this.ensureEventFileExists(join(dir, eventName)); + const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(join(this.getEventDir(eventType), eventType)).toString(); + const eventContents = fs.readFileSync(dir).toString(); const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - if (this.eventTimes.get(eventType) !== eventTime) { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventType}`); + if (this.eventTimes.get(eventName) !== eventTime) { + this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventName}`); // Promise.all(callbacks) callbacks.forEach(cb => cb()); - this.eventTimes.set(eventType, eventTime); + this.eventTimes.set(eventName, eventTime); } }); - this.subscriptions.set(eventType, [watcher, callbacks]); + this.subscriptions.set(eventName, [watcher, callbacks]); return watcher; } /** - * Check to see if the given event is a User event - * @param eventType A string representing the type of event - * @returns True if it is a user event, false otherwise - */ - public isUserEvent(eventType: string): eventType is ImperativeEventType { - return Object.values(ImperativeUserEvents).includes(eventType); - } - - /** - * Check to see if the given event is a shared event - * @param eventType A string representing the type of event - * @returns True if it is a shared event, false otherwise - */ - public isSharedEvent(eventType: string): eventType is ImperativeEventType { - return Object.values(ImperativeSharedEvents).includes(eventType); - } - - /** - * Check to see if the given event is a Custom event - * @param eventType A string representing the type of event - * @returns True if it is not a zowe or a user event, false otherwise - * @internal Not implemented in the MVP - */ - public isCustomEvent(eventType: string): eventType is ImperativeEventType { - return !this.isUserEvent(eventType) && !this.isSharedEvent(eventType); - } - - /** - * ZOWE HOME directory to search for system wide ImperativeEvents like `configChanged` - */ - public getSharedEventDir(): string { - return join(ImperativeConfig.instance.cliHome, ".events"); - } - - /** - * USER HOME directory to search for user specific ImperativeEvents like `vaultChanged` + * Returns the eventType based on eventName + * @param eventName Name of event, ie: onSchemaChanged + * @param isCustomShared One of the ImperativeEventTypes from ImperativeEventConstants */ - public getUserEventDir(): string { - return join(homedir(), ".zowe", ".events"); + private getEventType(eventName: string, isCustomShared: boolean): ImperativeEventTypes { + if (isCustomShared) + return ImperativeCustomShared; + if ( Object.values(ImperativeUserEvents).includes(eventName)){ + return ImperativeUserEvents; + } + if (Object.values(ImperativeSharedEvents).includes(eventName)){ + return ImperativeSharedEvents; + } + return ImperativeCustomUser; } - /** - * Obtain the directory of the event - * @param eventType The type of event to be emitted - * @returns The directory to where this event will be emitted - */ - public getEventDir(eventType: string): string { - if (this.isUserEvent(eventType)) { - return this.getUserEventDir(); - } else if (this.isSharedEvent(eventType)) { - return this.getSharedEventDir(); + public static get instance(): ImperativeEventEmitter { + if (this.mInstance == null) { + this.mInstance = new ImperativeEventEmitter(); + this.mInstance.initialize(); + this.mInstance.subscriptions = new Map(); + this.mInstance.eventTimes = new Map(); } - - return this.getSharedEventDir(); + return this.mInstance; } /** - * Simple method to write the events to disk - * @param eventType The type of event to write - * @internal We do not want to make this function accessible to any application developers + * Returns the directory path based on EventType + * @param eventName Name of event, ie: onSchemaChanged + * @param eventType One of the ImperativeEventTypes from ImperativeEventConstants + * @param appName Needed for custom event path */ - public emitEvent(eventType: ImperativeEventType) { - const theEvent = this.initEvent(eventType); - - if (this.isCustomEvent(eventType)) { - throw new ImperativeError({ msg: `Unable to determine the type of event. Event: ${eventType}` }); + public getEventDir(eventName: string, eventType: ImperativeEventTypes, appName: string): string { + switch (eventType) { + case ImperativeSharedEvents: + return join(ImperativeConfig.instance.cliHome, ".zowe", ".events", eventName); + case ImperativeCustomShared: + return join(ImperativeConfig.instance.cliHome, ".zowe", ".events", appName, eventName); + case ImperativeCustomUser: + return join(ImperativeConfig.instance.cliHome, ".events", appName, eventName); + default: + //ImperativeUserEvents + return join(ImperativeConfig.instance.cliHome, ".events", eventName); } - - this.writeEvent(this.getEventDir(eventType), theEvent); } /** * Simple method to write the events to disk - * @param eventType The type of event to write - * @internal We won't support custom events as part of the MVP + * @param eventName The name of event to write + * @internal We do not want to make this function accessible to any application developers */ - public emitCustomEvent(eventType: string) { //, isUserSpecific: boolean = false) { - const theEvent = this.initEvent(eventType); - - if (!this.isCustomEvent(eventType)) { - throw new ImperativeError({ msg: `Operation not allowed. Event is considered protected. Event: ${eventType}` }); - } - - this.writeEvent(this.getSharedEventDir(), theEvent); + public emitEvent(eventName: string) { + const theEvent = this.initEvent(eventName); + this.writeEvent(this.loc, theEvent); } /** * Method to register your custom actions based on when the given event is emitted - * @param eventType Type of event to register - * @param callback Action to be registered to the given event + * @param eventName name of event to register custom action to + * @param callback Custom action to be registered to the given event */ - public subscribe(eventType: string, callback: Function): IImperativeRegisteredAction { + public subscribe(eventName: string, callback: Function): IImperativeRegisteredAction { this.ensureClassInitialized(); let watcher: fs.FSWatcher; - if (this.subscriptions.get(eventType) != null) { - const [watcherToClose, callbacks] = this.subscriptions.get(eventType); - watcherToClose.removeAllListeners(eventType).close(); + if (this.subscriptions.get(eventName) != null) { + const [watcherToClose, callbacks] = this.subscriptions.get(eventName); + watcherToClose.removeAllListeners(eventName).close(); - watcher = this.setupWatcher(eventType, [...callbacks, callback]); + watcher = this.setupWatcher(eventName, [...callbacks, callback]); } else { - watcher = this.setupWatcher(eventType, [callback]); + watcher = this.setupWatcher(eventName, [callback]); } return { close: watcher.close }; } /** - * Method to unsubscribe from custom and regular events + * Method to unsubscribe from any given event * @param eventType Type of registered event */ public unsubscribe(eventType: string): void { diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts index 6f60d1f82f..5463874e4a 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventJson.ts @@ -9,6 +9,8 @@ * */ +import { ImperativeEventTypes } from "../ImperativeEventConstants"; + /** * Imperative Event JSON representation * @export @@ -19,10 +21,14 @@ export interface IImperativeEventJson { * The time in which the event occurred */ time: string; + /** + * The name of event that occurred + */ + name: string; /** * The type of event that occurred */ - type: string; + type: ImperativeEventTypes; /** * The application name that triggered the event */ @@ -32,11 +38,11 @@ export interface IImperativeEventJson { */ id?: string; /** - * The location in which the event was emitted (User vs Shared) + * The file path for information on the emitted event */ loc?: string; /** * The indicator of user-specific (if true) or shared (if false) events */ - user?: boolean; + isCustomShared?: boolean; } diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts index e66efa5d4c..b025472367 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts @@ -10,7 +10,7 @@ */ import { Logger } from "../../../logger"; -import { ImperativeEventType } from "../ImperativeEventConstants"; +import { ImperativeEventTypes } from "../ImperativeEventConstants"; /** * Imperative Standard Event @@ -29,13 +29,19 @@ export interface IImperativeEventParms { * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - eventType: ImperativeEventType | string + eventType: ImperativeEventTypes + /** + * Path for the event file + * @type {ImperativeEventType} + * @memberof IImperativeEventParms + */ + loc: string /** * Specifies whether this is a user event or not * @type {ImperativeEventType} * @memberof IImperativeEventParms */ - isUser: boolean + isCustomShared: boolean /** * The logger to use when logging the imperative event that occurred * @type {Logger} From cab176da7256c295770b619cd2228772df1fa5a6 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 29 Apr 2024 16:27:33 -0400 Subject: [PATCH 02/72] private to public chane Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 21d1df887b..7b278d55da 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -40,7 +40,7 @@ export class ImperativeEventEmitter { public isCustomShared: boolean; public logger: Logger; - private initialize(appName?: string, options?: IImperativeEventEmitterOpts) { + public initialize(appName?: string, options?: IImperativeEventEmitterOpts) { if (this.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); } From 284598ed1248ec242768eefb9da0b7c039ea2827 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 29 Apr 2024 16:57:32 -0400 Subject: [PATCH 03/72] unsure if these changes are needed Signed-off-by: Amber Torrise --- .../imperative/src/events/src/ImperativeEventEmitter.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 7b278d55da..321d2d24ce 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -27,9 +27,6 @@ import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEv import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { - static initialize(app: string, arg1: { logger: Logger; }) { - throw new Error("Method not implemented."); - } private static mInstance: ImperativeEventEmitter; private initialized = false; private subscriptions: Map; @@ -166,16 +163,16 @@ export class ImperativeEventEmitter { return ImperativeCustomUser; } + public static get instance(): ImperativeEventEmitter { - if (this.mInstance == null) { + if (!this.mInstance) { this.mInstance = new ImperativeEventEmitter(); this.mInstance.initialize(); - this.mInstance.subscriptions = new Map(); - this.mInstance.eventTimes = new Map(); } return this.mInstance; } + /** * Returns the directory path based on EventType * @param eventName Name of event, ie: onSchemaChanged From 41d3b0c89a4e2dc5dc3e4047f29adc29c2f6f437 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 09:46:10 -0400 Subject: [PATCH 04/72] separating initalization from instance creation Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEventEmitter.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 321d2d24ce..9c8be5ef49 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -37,6 +37,15 @@ export class ImperativeEventEmitter { public isCustomShared: boolean; public logger: Logger; + // instance creation + public static get instance(): ImperativeEventEmitter { + if (!this.mInstance) { + this.mInstance = new ImperativeEventEmitter(); + } + return this.mInstance; + } + + // initialization public initialize(appName?: string, options?: IImperativeEventEmitterOpts) { if (this.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); @@ -163,16 +172,6 @@ export class ImperativeEventEmitter { return ImperativeCustomUser; } - - public static get instance(): ImperativeEventEmitter { - if (!this.mInstance) { - this.mInstance = new ImperativeEventEmitter(); - this.mInstance.initialize(); - } - return this.mInstance; - } - - /** * Returns the directory path based on EventType * @param eventName Name of event, ie: onSchemaChanged From 31a112565904d7f909a5378e09254060c76d6c11 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 09:55:49 -0400 Subject: [PATCH 05/72] possibly breaking things XD Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index decda488ff..df4935a547 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() }); + ImperativeEventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); // Populate configuration file layers await myNewConfig.reload(opts); From 9cfb6feb206b9718090b9dcbe7c9ee4ab605c6cd Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 10:13:21 -0400 Subject: [PATCH 06/72] tinkering Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 2 +- .../src/events/src/ImperativeEventEmitter.ts | 21 ++++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index df4935a547..decda488ff 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - ImperativeEventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); + ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() }); // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 9c8be5ef49..f6b8653f06 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -22,40 +22,31 @@ import { } from "./ImperativeEventConstants"; import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger/src/Logger"; -import { LoggerManager } from "../../logger/src/LoggerManager"; import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc"; -import { ConfigUtils } from "../../config/src/ConfigUtils"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private initialized = false; + public appName: string; + public logger: Logger; private subscriptions: Map; private eventTimes: Map; - public appName: string; public eventType: ImperativeEventTypes; public loc: string; public isCustomShared: boolean; - public logger: Logger; - // instance creation public static get instance(): ImperativeEventEmitter { - if (!this.mInstance) { + if (this.mInstance == null) { this.mInstance = new ImperativeEventEmitter(); } return this.mInstance; } - // initialization - public initialize(appName?: string, options?: IImperativeEventEmitterOpts) { - if (this.initialized) { + public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { + if (this.instance.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); } - this.initialized = true; - - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ConfigUtils.initImpUtils("zowe"); - } - + this.instance.initialized = true; ImperativeEventEmitter.instance.appName = appName; ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } From fa6649dfdc17ea52fa9a7852a6d04efbf3da4671 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 10:34:47 -0400 Subject: [PATCH 07/72] tinkering pt 2! Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 2 +- .../src/events/src/ImperativeEventEmitter.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index decda488ff..df4935a547 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - ImperativeEventEmitter.initialize(app, { logger:Logger.getAppLogger() }); + ImperativeEventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index f6b8653f06..b0ed4c89a3 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -27,10 +27,10 @@ import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEv export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private initialized = false; - public appName: string; - public logger: Logger; private subscriptions: Map; private eventTimes: Map; + public appName: string; + public logger: Logger; public eventType: ImperativeEventTypes; public loc: string; public isCustomShared: boolean; @@ -42,11 +42,11 @@ export class ImperativeEventEmitter { return this.mInstance; } - public static initialize(appName?: string, options?: IImperativeEventEmitterOpts) { - if (this.instance.initialized) { + public initialize(appName?: string, options?: IImperativeEventEmitterOpts) { + if (this.initialized) { throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); } - this.instance.initialized = true; + this.initialized = true; ImperativeEventEmitter.instance.appName = appName; ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } From 5af16ea64b00bc45a69a36b834004c706d659eae Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 10:52:03 -0400 Subject: [PATCH 08/72] fixin Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/ImperativeEventEmitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index b0ed4c89a3..33fffd591a 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -27,7 +27,7 @@ import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEv export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private initialized = false; - private subscriptions: Map; + private subscriptions: Map = new Map(); private eventTimes: Map; public appName: string; public logger: Logger; From bfdb9e270b680c9d34979d8ded8199e192610337 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 30 Apr 2024 16:46:52 -0400 Subject: [PATCH 09/72] poc works with this branch now, minus unscubscribe Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEventEmitter.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 33fffd591a..9502155211 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -10,7 +10,7 @@ */ import * as fs from "fs"; -import { join } from "path"; +import { join, dirname } from "path"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error/src/ImperativeError"; import { @@ -23,6 +23,7 @@ import { import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger/src/Logger"; import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc"; +import { ProfileInfo } from "../../config"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; @@ -98,7 +99,7 @@ export class ImperativeEventEmitter { return new ImperativeEvent({ appName: this.appName, eventType: this.getEventType(eventName, this.isCustomShared), - loc: this.getEventDir(eventName, this.eventType, this.appName), + loc: this.getEventDir(this.eventType, this.appName), isCustomShared: this.isCustomShared, logger: this.logger }); @@ -125,7 +126,7 @@ export class ImperativeEventEmitter { * @returns The FSWatcher instance created */ private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { - const dir = this.getEventDir(eventName, this.eventType, this.appName); + const dir = this.getEventDir(this.eventType, this.appName); this.loc = dir; this.ensureEventsDirExists(dir); //ensure .events exist @@ -169,17 +170,17 @@ export class ImperativeEventEmitter { * @param eventType One of the ImperativeEventTypes from ImperativeEventConstants * @param appName Needed for custom event path */ - public getEventDir(eventName: string, eventType: ImperativeEventTypes, appName: string): string { + public getEventDir(eventType: ImperativeEventTypes, appName: string): string { switch (eventType) { case ImperativeSharedEvents: - return join(ImperativeConfig.instance.cliHome, ".zowe", ".events", eventName); + return join(ProfileInfo.getZoweDir(), ".events"); case ImperativeCustomShared: - return join(ImperativeConfig.instance.cliHome, ".zowe", ".events", appName, eventName); + return join(ProfileInfo.getZoweDir(),".events", appName); case ImperativeCustomUser: - return join(ImperativeConfig.instance.cliHome, ".events", appName, eventName); + return join(dirname(ProfileInfo.getZoweDir()), ".events", appName); default: //ImperativeUserEvents - return join(ImperativeConfig.instance.cliHome, ".events", eventName); + return join(dirname(ProfileInfo.getZoweDir()), ".events"); } } From 7ffb3feb6e0f269c1ec4f5dae1623dcc38b586a1 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Fri, 3 May 2024 17:23:25 -0400 Subject: [PATCH 10/72] WIPgit add . Signed-off-by: Amber Torrise --- .../src/events/src/ImperativeEvent.ts | 63 +---- .../events/src/ImperativeEventConstants.ts | 13 +- .../src/events/src/ImperativeEventEmitter.ts | 226 ++++-------------- .../imperative/src/events/src/Utilities.ts | 183 ++++++++++++++ .../events/src/doc/IEventSubscriptionParms.ts | 58 +++++ .../events/src/doc/IImperativeEventParms.ts | 51 ---- .../imperative/src/events/src/doc/index.ts | 2 +- 7 files changed, 305 insertions(+), 291 deletions(-) create mode 100644 packages/imperative/src/events/src/Utilities.ts create mode 100644 packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts delete mode 100644 packages/imperative/src/events/src/doc/IImperativeEventParms.ts diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts index 6efba489eb..dbd0268d08 100644 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ b/packages/imperative/src/events/src/ImperativeEvent.ts @@ -20,60 +20,35 @@ import { ImperativeEventTypes } from "./ImperativeEventConstants"; */ export class ImperativeEvent { /** - * The ID of the event - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mEventID: string; - - /** - * The application ID that caused this event + * The name of event that occurred * @private * @type {string} * @memberof ImperativeEvent */ - private mAppID: string; - + private mEventName: string; /** - * The time of the event created with new Date().toISOString() (ISO String) + * The application name that caused this event * @private * @type {string} * @memberof ImperativeEvent */ - private mEventTime: string; - + private mAppName: string; /** - * The name of event that occurred + * The type(ImperativeEventTypes) of event that occurred * @private * @type {string} - * @memberof ImperativeEvent + * @memberof ImperativeEventTypes */ - private mEventName: string; - + private mEventType: string; /** - * The type of event that occurred + * The time of the event created with new Date().toISOString() (ISO String) * @private * @type {string} * @memberof ImperativeEvent */ - private mEventType: ImperativeEventTypes; + private mEventTime: string; - /** - * File path for the event - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mLoc: string; - /** - * Indicator of custom ( A.K.A. 'extender') shared events as opposed to standard user or shared events - * @private - * @type {boolean} - * @memberof ImperativeEvent - */ - private isCustomShared: boolean; /** * toString overload to be called automatically on string concatenation @@ -91,21 +66,13 @@ export class ImperativeEvent { return { time: this.eventTime, name: this.eventName, - type: this.eventType, source: this.appName, - id: this.eventId, - loc: this.loc, - isCustomShared: this.isCustomShared, }; }; constructor(parms: IImperativeEventParms) { this.mEventTime = new Date().toISOString(); - this.mEventID = randomUUID(); this.mAppID = parms.appName; - this.mEventType = parms.eventType; - this.mLoc = parms.loc; - this.isCustomShared = parms.isCustomShared; parms.logger.debug("ImperativeEvent: " + this); } @@ -117,19 +84,11 @@ export class ImperativeEvent { return this.mEventName; } - public get eventType(): ImperativeEventTypes { - return this.mEventType; - } - public get appName(): string { return this.mAppID; } - public get loc(): string { - return this.mLoc; - } - - public get eventId() : string { - return this.mEventID; + public get path(): string { + return this.mPath; } } diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/ImperativeEventConstants.ts index 904200de63..6d470af578 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/ImperativeEventConstants.ts @@ -9,6 +9,9 @@ * */ + +// TO DO - flesh out these enums to include all expected user and shared events + export enum ImperativeUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } @@ -31,16 +34,8 @@ export type ImperativeEventTypes = typeof ImperativeCustomUser; /** - * TODO: - * The following list of event types will only be implemented upon request - * - * BRAINSTORMING - What is needed multiple times that we need to keep track of? - * - project name - * - event name - * - app name - * - shared event (boolean) + * EXPECTED EVENT LOCATIONS: * - * how are we determining if global or project?? * Shared events: * Global: * - $ZOWE_CLI_HOME/.events/onConfigChanged diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/ImperativeEventEmitter.ts index 9502155211..70ce7e560d 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/ImperativeEventEmitter.ts @@ -9,32 +9,21 @@ * */ -import * as fs from "fs"; -import { join, dirname } from "path"; -import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; -import { ImperativeError } from "../../error/src/ImperativeError"; -import { - ImperativeEventTypes, - ImperativeUserEvents, - ImperativeSharedEvents, - ImperativeCustomShared, - ImperativeCustomUser -} from "./ImperativeEventConstants"; -import { ImperativeEvent } from "./ImperativeEvent"; import { Logger } from "../../logger/src/Logger"; -import { IImperativeRegisteredAction, IImperativeEventEmitterOpts, IImperativeEventJson } from "./doc"; -import { ProfileInfo } from "../../config"; +import { + IImperativeRegisteredAction, + IImperativeEventEmitterOpts, + IEventSubscriptionParms +} from "./doc"; +import { ImperativeError } from "../../error/src/ImperativeError"; +import * as Utilities from "./Utilities"; export class ImperativeEventEmitter { private static mInstance: ImperativeEventEmitter; private initialized = false; - private subscriptions: Map = new Map(); - private eventTimes: Map; + private subscriptions: Map< string, IEventSubscriptionParms> = new Map(); public appName: string; public logger: Logger; - public eventType: ImperativeEventTypes; - public loc: string; - public isCustomShared: boolean; public static get instance(): ImperativeEventEmitter { if (this.mInstance == null) { @@ -52,137 +41,6 @@ export class ImperativeEventEmitter { ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); } - /** - * Check to see if the Imperative Event Emitter instance has been initialized - */ - private ensureClassInitialized() { - if (!this.initialized) { - throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); - } - } - - /** - * Check to see if the directory exists, otherwise, create it : ) - * @param directoryPath Zowe or User dir where we will write the events - */ - private ensureEventsDirExists(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } - } - - /** - * Check to see if the file path exists, otherwise, create it : ) - * @param filePath Zowe or User path where we will write the events - */ - private ensureEventFileExists(filePath: string) { - try { - if (!fs.existsSync(filePath)) { - fs.closeSync(fs.openSync(filePath, 'w')); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); - } - } - - /** - * Helper method to initialize the event - * @param eventName The name of event to initialize - * @returns The initialized ImperativeEvent - */ - private initEvent(eventName: string): ImperativeEvent { - this.ensureClassInitialized(); - return new ImperativeEvent({ - appName: this.appName, - eventType: this.getEventType(eventName, this.isCustomShared), - loc: this.getEventDir(this.eventType, this.appName), - isCustomShared: this.isCustomShared, - logger: this.logger - }); - } - - /** - * Helper method to write contents out to disk - * @param location directory to write the file (i.e. emit the event) - * @param event the event to be written/emitted - * @internal We do not want developers writing events directly, they should use the `emit...` methods - */ - private writeEvent(location: string, event: ImperativeEvent) { - const eventPath = join(location, (event.eventType).toString()); - const eventJson = { ...event.toJson(), loc: location }; - - this.ensureEventsDirExists(location); - fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); - } - - /** - * Helper method to create watchers based on event strings and list of callbacks - * @param eventName name of event to which we will create a watcher for - * @param callbacks list of all callbacks for this watcher - * @returns The FSWatcher instance created - */ - private setupWatcher(eventName: string, callbacks: Function[] = []): fs.FSWatcher { - const dir = this.getEventDir(this.eventType, this.appName); - this.loc = dir; - this.ensureEventsDirExists(dir); //ensure .events exist - - this.ensureEventFileExists(join(dir, eventName)); - const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { - // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(dir).toString(); - const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - - if (this.eventTimes.get(eventName) !== eventTime) { - this.logger.debug(`ImperativeEventEmitter: Event "${event}" emitted: ${eventName}`); - // Promise.all(callbacks) - callbacks.forEach(cb => cb()); - this.eventTimes.set(eventName, eventTime); - } - }); - this.subscriptions.set(eventName, [watcher, callbacks]); - return watcher; - } - - /** - * Returns the eventType based on eventName - * @param eventName Name of event, ie: onSchemaChanged - * @param isCustomShared One of the ImperativeEventTypes from ImperativeEventConstants - */ - private getEventType(eventName: string, isCustomShared: boolean): ImperativeEventTypes { - if (isCustomShared) - return ImperativeCustomShared; - if ( Object.values(ImperativeUserEvents).includes(eventName)){ - return ImperativeUserEvents; - } - if (Object.values(ImperativeSharedEvents).includes(eventName)){ - return ImperativeSharedEvents; - } - return ImperativeCustomUser; - } - - /** - * Returns the directory path based on EventType - * @param eventName Name of event, ie: onSchemaChanged - * @param eventType One of the ImperativeEventTypes from ImperativeEventConstants - * @param appName Needed for custom event path - */ - public getEventDir(eventType: ImperativeEventTypes, appName: string): string { - switch (eventType) { - case ImperativeSharedEvents: - return join(ProfileInfo.getZoweDir(), ".events"); - case ImperativeCustomShared: - return join(ProfileInfo.getZoweDir(),".events", appName); - case ImperativeCustomUser: - return join(dirname(ProfileInfo.getZoweDir()), ".events", appName); - default: - //ImperativeUserEvents - return join(dirname(ProfileInfo.getZoweDir()), ".events"); - } - } /** * Simple method to write the events to disk @@ -190,44 +48,56 @@ export class ImperativeEventEmitter { * @internal We do not want to make this function accessible to any application developers */ public emitEvent(eventName: string) { - const theEvent = this.initEvent(eventName); - this.writeEvent(this.loc, theEvent); + const event = Utilities.initEvent(eventName); + Utilities.writeEvent(event.path, event); } + /** - * Method to register your custom actions based on when the given event is emitted + * Method to register your custom actions to a given event emission * @param eventName name of event to register custom action to - * @param callback Custom action to be registered to the given event + * @param eventParms passed along parms to contribute to the overall event subscription data object */ - public subscribe(eventName: string, callback: Function): IImperativeRegisteredAction { - this.ensureClassInitialized(); - - let watcher: fs.FSWatcher; - if (this.subscriptions.get(eventName) != null) { - const [watcherToClose, callbacks] = this.subscriptions.get(eventName); - watcherToClose.removeAllListeners(eventName).close(); - - watcher = this.setupWatcher(eventName, [...callbacks, callback]); + public subscribe(eventName: string, eventParms: IEventSubscriptionParms): IImperativeRegisteredAction { + // HELP - why are we returning subscription.watcher.close? + Utilities.ensureClassInitialized(); + const subscription = this.subscriptions.get(eventName); + if (subscription != null){ + // modify existing subscription + if (subscription.watcher != null){ + const watcherToClose = subscription.watcher; + watcherToClose.removeAllListeners(eventName).close(); + } + this.subscriptions.set(eventName, { + ...subscription, + //HELP - not sure if we should keep old callbacks? or overwrite with new: + callbacks: [...subscription.callbacks, ...eventParms.callbacks], + watcher: Utilities.setupWatcher(eventName), + isCustomShared: eventParms.isCustomShared, + eventTime: new Date().toISOString(), + }); } else { - watcher = this.setupWatcher(eventName, [callback]); + // create new subscription + this.subscriptions.set(eventName, { + callbacks: eventParms.callbacks, //callback has to be set before watcher + watcher: Utilities.setupWatcher(eventName), + eventType: Utilities.getEventType(eventName), + isCustomShared: eventParms.isCustomShared, + eventTime: new Date().toISOString(), + dir: Utilities.getEventDir(Utilities.getEventType(eventName), this.appName) + }); } - return { close: watcher.close }; + return { close: subscription.watcher.close }; } /** - * Method to unsubscribe from any given event - * @param eventType Type of registered event + * Unsubscribe from any given event + * @param eventName name of event */ - public unsubscribe(eventType: string): void { - this.ensureClassInitialized(); - - if (this.subscriptions.has(eventType)) { - const [watcherToClose, _callbacks] = this.subscriptions.get(eventType); - watcherToClose.removeAllListeners(eventType).close(); - this.subscriptions.delete(eventType); - } - if (this.eventTimes.has(eventType)) { - this.eventTimes.delete(eventType); + public unsubscribe(eventName: string): void { + Utilities.ensureClassInitialized(); + if (this.subscriptions.has(eventName)) { + this.subscriptions.get(eventName).watcher.removeAllListeners(eventName).close(); + this.subscriptions.delete(eventName); } } -} \ No newline at end of file diff --git a/packages/imperative/src/events/src/Utilities.ts b/packages/imperative/src/events/src/Utilities.ts new file mode 100644 index 0000000000..8c72c8fbf1 --- /dev/null +++ b/packages/imperative/src/events/src/Utilities.ts @@ -0,0 +1,183 @@ +// move over the following methods +// getEventDir +// getEventType +// ensureEventFileExists +// ensureEventsDirExists +// ensureClassInitialized +// setupWatcher +// initEvent +// writeEvent + +import * as fs from "fs"; +import { join, dirname } from "path"; +import { ImperativeError } from "../../error/src/ImperativeError"; +import { + ImperativeUserEvents, + ImperativeSharedEvents, + ImperativeCustomShared, + ImperativeCustomUser, + ImperativeEventTypes +} from "./ImperativeEventConstants"; +import { IImperativeEventJson, IEventSubscriptionParms } from "./doc"; +import { ProfileInfo } from "../../config"; +import { ImperativeEvent } from "./ImperativeEvent"; + +//////////// Subscription Helpers /////////////////////////////////////////////////////////////////////////// +/** + * Helper method to create watchers based on event strings and list of callbacks + * @param eventName name of event to which we will create a watcher for + * @param callbacks list of all callbacks for this watcher + * @returns The FSWatcher instance created + */ +export function setupWatcher(eventName: string): fs.FSWatcher { + const subscription = this.subscriptions.get(eventName); + const dir = join(ProfileInfo.getZoweDir(), subscription.dir); + this.ensureEventsDirExists(dir); + this.ensureEventFileExists(join(dir, eventName)); + + const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { + // Node.JS triggers this event 3 times + const eventContents = fs.readFileSync(dir).toString(); + const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; + if (subscription.eventTime !== eventTime) { + callbacks.forEach(callback => callback()); + this.subscriptions.set(eventName, { + ...subscription, + eventTime: eventTime + }); + } + }); + // Update the map with the new watcher and callbacks + this.subscriptions.set(eventName, { + ...subscription, + watcher: watcher, + callbacks: [...subscription.callbacks, callbacks] + }); + return watcher; +} + + +//////////// Emit Helpers ////////////////////////////////////////////////////////////////////////////////// +/** + * Helper method to initialize the event for {@link ImperativeEventEmitter.emitEvent} + * @param eventName The name of event to initialize + * @returns The initialized ImperativeEvent + */ +export function initEvent(eventName: string, parms: IEventSubscriptionParms ): ImperativeEvent { + this.ensureClassInitialized(); + return new ImperativeEvent({ + appName: this.appName, + eventName: eventName, + eventType: this.getEventType(eventName), + eventTime: new Date().toISOString(), + path: join(ProfileInfo.getZoweDir() ,this.getEventDir(parms.eventType, this.appName)), + isCustomShared: parms.isCustomShared, + logger: this.logger + }); +} + +/** + * Helper method to write contents out to disk for {@link ImperativeEventEmitter.emitEvent} + * @param location directory to write the file (i.e. emit the event) + * @param event the event to be written/emitted + * @internal We do not want developers writing events directly, they should use the `emit...` methods + */ +export function writeEvent(location: string, event: ImperativeEvent) { + const eventPath = join(location, (event.eventType).toString()); + const eventJson = { ...event.toJson(), loc: location }; + + this.ensureEventsDirExists(location); + fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); +} + +//////////// Initialization Helpers ///////////////////////////////////////////////////////////////////////// +/** + * Check to see if the Imperative Event Emitter instance has been initialized + */ +export function ensureClassInitialized() { + if (!this.initialized) { + throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); + } +} + +/** + * Check to see if the directory exists, otherwise, create it : ) + * @param directoryPath Zowe or User dir where we will write the events + */ +export function ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } +} + +/** + * Check to see if the file path exists, otherwise, create it : ) + * @param filePath Zowe or User path where we will write the events + */ +export function ensureEventFileExists(filePath: string) { + try { + if (!fs.existsSync(filePath)) { + fs.closeSync(fs.openSync(filePath, 'w')); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); + } +} + +/** + * Sets and returns the eventType based on eventName + * @param eventName Name of event, ie: onSchemaChanged + */ +export function getEventType(eventName: string): ImperativeEventTypes { + const isCustomShared = this.subscriptions.get(eventName).isCustomShared; + if (isCustomShared){ + // this.subscriptions.set(eventName, { + // ...this.subscriptions.get(eventName), + // eventType: ImperativeCustomShared + // }); + return ImperativeCustomShared; + } + if ( Object.values(ImperativeUserEvents).includes(eventName)){ + // this.subscriptions.set(eventName, { + // ...this.subscriptions.get(eventName), + // eventType: ImperativeUserEvents + // }); + return ImperativeUserEvents; + } + if (Object.values(ImperativeSharedEvents).includes(eventName)){ + // this.subscriptions.set(eventName, { + // ...this.subscriptions.get(eventName), + // eventType: ImperativeSharedEvents + // }); + return ImperativeSharedEvents; + } + // this.subscriptions.set(eventName, { + // ...this.subscriptions.get(eventName), + // eventType: ImperativeCustomUser + // }); + return ImperativeCustomUser; +} + +/** + * Returns the directory path based on EventType + * @param eventName Name of event, ie: onSchemaChanged + * @param eventType One of the ImperativeEventTypes from ImperativeEventConstants + * @param appName Needed for custom event path + */ +export function getEventDir(eventType: ImperativeEventTypes, appName: string): string { + switch (eventType) { + case ImperativeSharedEvents: + return join(ProfileInfo.getZoweDir(), ".events"); + case ImperativeCustomShared: + return join(ProfileInfo.getZoweDir(),".events", appName); + case ImperativeCustomUser: + return join(dirname(ProfileInfo.getZoweDir()), ".events", appName); + default: + //ImperativeUserEvents + return join(dirname(ProfileInfo.getZoweDir()), ".events"); + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts b/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts new file mode 100644 index 0000000000..4d299e1567 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts @@ -0,0 +1,58 @@ +/* +* 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 { ImperativeEventTypes } from "../ImperativeEventConstants"; +import { FSWatcher } from "fs"; + +/** + * Imperative Registered Action + * @export + * @interface IEventSubscriptionParms + */ +export interface IEventSubscriptionParms { + /** + * The type of event that occurred + * @type {ImperativeEventTypes} + * @memberof IEventSubscriptionParms + */ + eventType?: ImperativeEventTypes + /** + * The time of the latest event occurrence + * @type {string} + * @memberof IEventSubscriptionParms + */ + eventTime?: string + /** + * Specifies whether this is a custom shared event, necessary for extenders to set + * @type {boolean} + * @memberof IEventSubscriptionParms + */ + isCustomShared?: boolean + /** + * Event dir for the .event file + * Incomplete dir path to be joined with the current value stored in zoweDir + * @type {string} + * @memberof IEventSubscriptionParms + */ + dir?: string; + /** + * The attached watcher for this subscription + * @type {FSWatcher} + * @memberof IEventSubscriptionParms + */ + watcher?: FSWatcher + /** + * Functions to trigger upon event emission + * @type {Function[]} + * @memberof IEventSubscriptionParms + */ + callbacks?: Function[] +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts b/packages/imperative/src/events/src/doc/IImperativeEventParms.ts deleted file mode 100644 index b025472367..0000000000 --- a/packages/imperative/src/events/src/doc/IImperativeEventParms.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* -* 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 { Logger } from "../../../logger"; -import { ImperativeEventTypes } from "../ImperativeEventConstants"; - -/** - * Imperative Standard Event - * @export - * @interface IImperativeEventParms - */ -export interface IImperativeEventParms { - /** - * The name of the application to be used to generate a unique ID for the event - * @type {string} - * @memberof IImperativeEventParms - */ - appName: string; - /** - * The type of imperative event that occurred - * @type {ImperativeEventType} - * @memberof IImperativeEventParms - */ - eventType: ImperativeEventTypes - /** - * Path for the event file - * @type {ImperativeEventType} - * @memberof IImperativeEventParms - */ - loc: string - /** - * Specifies whether this is a user event or not - * @type {ImperativeEventType} - * @memberof IImperativeEventParms - */ - isCustomShared: boolean - /** - * The logger to use when logging the imperative event that occurred - * @type {Logger} - * @memberof IImperativeEventParms - */ - logger: Logger; -} diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index 22eb9c922e..7516577fbb 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -10,6 +10,6 @@ */ export * from "./IImperativeEventEmitterOpts"; -export * from "./IImperativeEventParms"; export * from "./IImperativeRegisteredAction"; export * from "./IImperativeEventJson"; +export * from "./IEventSubscriptionParms"; From 721f6150e999dada84cf7430fb742c14630fdb95 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 9 May 2024 10:50:30 -0400 Subject: [PATCH 11/72] about to switch to an eventEmitterManager. what you see here is still a WIP of first iteration idea change Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 4 +- .../src/config/src/api/ConfigSecure.ts | 6 +- packages/imperative/src/events/index.ts | 6 +- .../src/events/src/EEmovingStuff.ts | 134 +++++++++++++ packages/imperative/src/events/src/Event.ts | 28 +++ ...iveEventConstants.ts => EventConstants.ts} | 15 +- ...erativeEventEmitter.ts => EventEmitter.ts} | 90 +++++++-- .../src/{Utilities.ts => EventUtilities.ts} | 184 ++++++++---------- .../src/events/src/ImperativeEvent.ts | 94 --------- ...entEmitterOpts.ts => IEventEmitterOpts.ts} | 6 +- ...{IImperativeEventJson.ts => IEventJson.ts} | 24 +-- .../events/src/doc/IEventSubscriptionParms.ts | 8 +- ...gisteredAction.ts => IRegisteredAction.ts} | 8 +- .../imperative/src/events/src/doc/index.ts | 6 +- .../security/src/CredentialManagerOverride.ts | 6 +- 15 files changed, 354 insertions(+), 265 deletions(-) create mode 100644 packages/imperative/src/events/src/EEmovingStuff.ts create mode 100644 packages/imperative/src/events/src/Event.ts rename packages/imperative/src/events/src/{ImperativeEventConstants.ts => EventConstants.ts} (82%) rename packages/imperative/src/events/src/{ImperativeEventEmitter.ts => EventEmitter.ts} (53%) rename packages/imperative/src/events/src/{Utilities.ts => EventUtilities.ts} (60%) delete mode 100644 packages/imperative/src/events/src/ImperativeEvent.ts rename packages/imperative/src/events/src/doc/{IImperativeEventEmitterOpts.ts => IEventEmitterOpts.ts} (80%) rename packages/imperative/src/events/src/doc/{IImperativeEventJson.ts => IEventJson.ts} (65%) rename packages/imperative/src/events/src/doc/{IImperativeRegisteredAction.ts => IRegisteredAction.ts} (70%) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index df4935a547..113f31c640 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,7 +31,7 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; -import { ImperativeEventEmitter } from "../../events"; +import { EventEmitter } from "../../events"; import { Logger } from "../../logger"; /** @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - ImperativeEventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); + EventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 765a8d430a..9d5fdc0517 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,8 +20,8 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; -import { ImperativeEventEmitter } from "../../../events/src/ImperativeEventEmitter"; -import { ImperativeUserEvents } from "../../../events/src/ImperativeEventConstants"; +import { EventEmitter } from "../../../events/src/EventEmitter"; +import { UserEvents } from "../../../events/src/EventConstants"; /** * API Class for manipulating config layers. @@ -132,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - ImperativeEventEmitter.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED); + EventEmitter.instance.emitEvent(UserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index 8da687cf65..d680f41f56 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -10,6 +10,6 @@ */ export * from "./src/doc"; -export * from "./src/ImperativeEvent"; -export * from "./src/ImperativeEventConstants"; -export * from "./src/ImperativeEventEmitter"; +export * from "./src/Event"; +export * from "./src/EventConstants"; +export * from "./src/EventEmitter"; diff --git a/packages/imperative/src/events/src/EEmovingStuff.ts b/packages/imperative/src/events/src/EEmovingStuff.ts new file mode 100644 index 0000000000..b17193b95f --- /dev/null +++ b/packages/imperative/src/events/src/EEmovingStuff.ts @@ -0,0 +1,134 @@ +/* +* 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 { ImperativeError } from "../../error/src/ImperativeError"; +import { join, dirname } from "path"; +import { Logger } from "../../logger/src/Logger"; +import { IEventJson, IRegisteredAction } from "./doc"; +import { Event } from "./Event"; +import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; +import { ProfileInfo } from "../../config/src/ProfileInfo"; +import * as fs from "fs"; +import { eventNames } from "process"; + +/** + * The EventEmitter class manages event emissions and subscriptions for different applications. + * It utilizes a scoped singleton pattern where instances are mapped by application names. + * Each instance maintains its own set of event subscriptions, allowing for efficient management + * of event-related data. +*/ +export class EventEmitter { + private static instances: Map = new Map(); + private subscriptions: Map = new Map(); + public appName: string; + public logger: Logger; + + private constructor(appName: string, logger: Logger) { + this.subscriptions = new Map(); + this.appName = appName; + this.logger = logger; + } + + public static Helpers = { + getInstance: function(appName: string, logger?: Logger): EventEmitter { + if (!EventEmitter.instances.has(appName)) { + const effectiveLogger = logger ?? Logger.getImperativeLogger(); + const newInstance = new EventEmitter(appName, effectiveLogger); + this.instances.set(appName, newInstance); + } + return this.instances.get(appName); + }, + getEventDetails: function(eventName: string): IEventJson | undefined { + return this.subscriptions.get(eventName); + }, + ensureEventsDirExists: function(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + }, + isUserEvent: function(eventName: string): boolean { + return Object.values(UserEvents).includes(eventName); + }, + isSharedEvent: function(eventName: string): boolean { + return Object.values(SharedEvents).includes(eventName); + }, + getEventDir: function(eventType: EventTypes, appName: string): string { + if (eventType == EventTypes.CustomSharedEvents || EventTypes.CustomUserEvents){ + return join(".events", appName); + } + return ".events"; + }, + createSubscription: function(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction{ + const dir = EventEmitter.Helpers.getEventDir(eventType, eeInst.appName); + EventEmitter.Helpers.ensureEventsDirExists(dir); + const filePath = join(dirname(ProfileInfo.getZoweDir()), eventName); + //possibly have to do some cleaning up of eventNames (custom ones might go crazy) + eeInst.subscriptions.set(eventName, { + eventTime: new Date().toISOString(), + eventName, + eventType, + appName: eeInst.appName, + eventFilePath: filePath + }); + // returns a disposable to automatically unsubscribe as cleanup when app closes + return { close: ()=> this.unsubscribe(eventName) }; + }, + writeEvent: function(event: Event) { + const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); + EventEmitter.Helpers.ensureEventsDirExists(eventPath); + fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); + } + }; + + public subscribeShared(eventName: string): void { + const isCustom = EventEmitter.Helpers.isSharedEvent(eventName); + const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; + const eeInst = EventEmitter.Helpers.getInstance(this.appName); + EventEmitter.Helpers.createSubscription(eeInst, eventName, eventType); + } + + public subscribeUser(eventName: string): void { + const isCustom = EventEmitter.Helpers.isUserEvent(eventName); + const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; + const eeInst = EventEmitter.Helpers.getInstance(this.appName); + EventEmitter.Helpers.createSubscription(eeInst, eventName, eventType); + } + + public emitEvent(eventName: string): void { + // search for correct event emitter instance based on appname. if there isnt one for that app, create it then search + // update the event file time for the event found via event.eventPath, write to the file + const event = EventEmitter.Helpers.getEventDetails(eventName); + if (!event) { + this.logger.error(`No subscription found for event: ${eventName}`); + return; + } + + const fullEvent = { + ...event, + eventTime: new Date().toISOString() // Update time to reflect the emission time + }; + EventEmitter.Helpers.writeEvent(); + this.logger.info(`Event emitted: ${JSON.stringify(fullEvent)}`); + } + + // * Intentional Unsubscription from given event + public unsubscribe(eventName: string): void { + if (this.subscriptions.has(eventName)) { + this.subscriptions.delete(eventName); + } else { + this.logger.error(`No subscription found for event: ${eventName}`); + } + } +} diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts new file mode 100644 index 0000000000..4063f8bf95 --- /dev/null +++ b/packages/imperative/src/events/src/Event.ts @@ -0,0 +1,28 @@ +import { EventTypes } from "./EventConstants"; +import { IEventJson } from "./doc"; + +export class Event implements IEventJson { + eventTime: string; + eventName: string; + eventType: EventTypes; + appName: string; + eventFilePath: string; + + constructor({ eventTime, eventName, eventType, appName, eventFilePath }: IEventJson) { + this.eventTime = eventTime; + this.eventName = eventName; + this.eventType = eventType; + this.appName = appName; + this.eventFilePath = eventFilePath; + } + + public toJson() { + return { + eventTime: this.eventTime, + eventName: this.eventName, + eventType: this.eventType, + appName: this.appName, + eventFilePath: this.eventFilePath + }; + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/ImperativeEventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts similarity index 82% rename from packages/imperative/src/events/src/ImperativeEventConstants.ts rename to packages/imperative/src/events/src/EventConstants.ts index 6d470af578..ebaa0a3b26 100644 --- a/packages/imperative/src/events/src/ImperativeEventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -12,27 +12,22 @@ // TO DO - flesh out these enums to include all expected user and shared events -export enum ImperativeUserEvents { +export enum UserEvents { ON_VAULT_CHANGED = "onVaultChanged" } -export enum ImperativeSharedEvents { +export enum SharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } -export enum ImperativeCustomShared { +export enum CustomSharedEvents { CUSTOM_SHARED_EVENT = "customSharedEvent" } -export enum ImperativeCustomUser { +export enum CustomUserEvents { CUSTOM_USER_EVENT = "customUserEvent", } -export type ImperativeEventTypes = - typeof ImperativeUserEvents | - typeof ImperativeSharedEvents | - typeof ImperativeCustomShared | - typeof ImperativeCustomUser; - +export enum EventTypes { UserEvents, SharedEvents, CustomSharedEvents, CustomUserEvents } /** * EXPECTED EVENT LOCATIONS: * diff --git a/packages/imperative/src/events/src/ImperativeEventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts similarity index 53% rename from packages/imperative/src/events/src/ImperativeEventEmitter.ts rename to packages/imperative/src/events/src/EventEmitter.ts index 70ce7e560d..537bde4031 100644 --- a/packages/imperative/src/events/src/ImperativeEventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -11,34 +11,77 @@ import { Logger } from "../../logger/src/Logger"; import { - IImperativeRegisteredAction, - IImperativeEventEmitterOpts, + IRegisteredAction, + IEventEmitterOpts, IEventSubscriptionParms } from "./doc"; import { ImperativeError } from "../../error/src/ImperativeError"; -import * as Utilities from "./Utilities"; +import * as Utilities from "./EventUtilities"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { LoggerManager } from "../../logger/src/LoggerManager"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; +import { unsubscribe } from "diagnostics_channel"; -export class ImperativeEventEmitter { - private static mInstance: ImperativeEventEmitter; - private initialized = false; +/** +*The EventEmitter class is a scoped singleton class maintained by a maps of instances based on +*application names as keys that keep track of event subscriptions(stored event properties) +*until subscription removal. +* +*Subscription model: event metadata is stored in a map upon subscription. +*Upon emission, the event is looked up and the event time is updated to the current time +*to reflect this new event. The time change can be used to implement fileWatcher +*callback functions. +*/ + +export class EventEmitter { + private static mInstance: EventEmitter; + private static initialized = false; private subscriptions: Map< string, IEventSubscriptionParms> = new Map(); public appName: string; public logger: Logger; - public static get instance(): ImperativeEventEmitter { + /** + /* Check to see if the Event Emitter instance has been initialized + */ + public static initialize(appName?: string, options?: IEventEmitterOpts) { + if (this.initialized) { + throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); + } + this.initialized = true; + + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ConfigUtils.initImpUtils("zowe"); + } + + EventEmitter.instance.appName = appName; + EventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); + } + + public static get instance(): EventEmitter { if (this.mInstance == null) { - this.mInstance = new ImperativeEventEmitter(); + this.mInstance = new EventEmitter(); + this.mInstance.subscriptions = new Map(); } return this.mInstance; } - public initialize(appName?: string, options?: IImperativeEventEmitterOpts) { - if (this.initialized) { - throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); + /** + * Check to see if the Imperative Event Emitter instance has been initialized + */ + private ensureClassInitialized() { + if (!EventEmitter.initialized) { + throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); } - this.initialized = true; - ImperativeEventEmitter.instance.appName = appName; - ImperativeEventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); + } + + private initEvent(eventName: string): Event { + this.ensureClassInitialized(); + return new Event({ + appName: this.appName, + eventName, + this.isCustomSharedEvent(eventName), + logger: this.logger + }); } @@ -48,8 +91,8 @@ export class ImperativeEventEmitter { * @internal We do not want to make this function accessible to any application developers */ public emitEvent(eventName: string) { - const event = Utilities.initEvent(eventName); - Utilities.writeEvent(event.path, event); + const event: Event = this.initEvent(eventName); + Utilities.writeEvent(event); } @@ -58,7 +101,7 @@ export class ImperativeEventEmitter { * @param eventName name of event to register custom action to * @param eventParms passed along parms to contribute to the overall event subscription data object */ - public subscribe(eventName: string, eventParms: IEventSubscriptionParms): IImperativeRegisteredAction { + public subscribe(eventName: string, eventParms: IEventSubscriptionParms): IRegisteredAction { // HELP - why are we returning subscription.watcher.close? Utilities.ensureClassInitialized(); const subscription = this.subscriptions.get(eventName); @@ -73,7 +116,7 @@ export class ImperativeEventEmitter { //HELP - not sure if we should keep old callbacks? or overwrite with new: callbacks: [...subscription.callbacks, ...eventParms.callbacks], watcher: Utilities.setupWatcher(eventName), - isCustomShared: eventParms.isCustomShared, + isCustomSharedEvent: eventParms.isCustomSharedEvent, eventTime: new Date().toISOString(), }); } else { @@ -82,16 +125,17 @@ export class ImperativeEventEmitter { callbacks: eventParms.callbacks, //callback has to be set before watcher watcher: Utilities.setupWatcher(eventName), eventType: Utilities.getEventType(eventName), - isCustomShared: eventParms.isCustomShared, + isCustomSharedEvent: eventParms.isCustomSharedEvent, eventTime: new Date().toISOString(), dir: Utilities.getEventDir(Utilities.getEventType(eventName), this.appName) }); } - return { close: subscription.watcher.close }; + // returns a disposable to automatically unsubscribe as cleanup when app closes + return { close: ()=> this.unsubscribe(eventName) }; } /** - * Unsubscribe from any given event + * Unsubscribe from given event * @param eventName name of event */ public unsubscribe(eventName: string): void { @@ -101,3 +145,7 @@ export class ImperativeEventEmitter { this.subscriptions.delete(eventName); } } +} + +// HELP -create an unsubscribe from all events for a given app? +// - do this by deleting eventEmitter instance and all its associated files? \ No newline at end of file diff --git a/packages/imperative/src/events/src/Utilities.ts b/packages/imperative/src/events/src/EventUtilities.ts similarity index 60% rename from packages/imperative/src/events/src/Utilities.ts rename to packages/imperative/src/events/src/EventUtilities.ts index 8c72c8fbf1..643b1b484b 100644 --- a/packages/imperative/src/events/src/Utilities.ts +++ b/packages/imperative/src/events/src/EventUtilities.ts @@ -12,87 +12,19 @@ import * as fs from "fs"; import { join, dirname } from "path"; import { ImperativeError } from "../../error/src/ImperativeError"; import { - ImperativeUserEvents, - ImperativeSharedEvents, - ImperativeCustomShared, - ImperativeCustomUser, - ImperativeEventTypes -} from "./ImperativeEventConstants"; -import { IImperativeEventJson, IEventSubscriptionParms } from "./doc"; + UserEvents, + SharedEvents, + CustomSharedEvents, + CustomUserEvents, + EventTypes +} from "./EventConstants"; +import { IEventJson, IEventSubscriptionParms } from "./doc"; import { ProfileInfo } from "../../config"; -import { ImperativeEvent } from "./ImperativeEvent"; - -//////////// Subscription Helpers /////////////////////////////////////////////////////////////////////////// -/** - * Helper method to create watchers based on event strings and list of callbacks - * @param eventName name of event to which we will create a watcher for - * @param callbacks list of all callbacks for this watcher - * @returns The FSWatcher instance created - */ -export function setupWatcher(eventName: string): fs.FSWatcher { - const subscription = this.subscriptions.get(eventName); - const dir = join(ProfileInfo.getZoweDir(), subscription.dir); - this.ensureEventsDirExists(dir); - this.ensureEventFileExists(join(dir, eventName)); - - const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { - // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(dir).toString(); - const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IImperativeEventJson).time; - if (subscription.eventTime !== eventTime) { - callbacks.forEach(callback => callback()); - this.subscriptions.set(eventName, { - ...subscription, - eventTime: eventTime - }); - } - }); - // Update the map with the new watcher and callbacks - this.subscriptions.set(eventName, { - ...subscription, - watcher: watcher, - callbacks: [...subscription.callbacks, callbacks] - }); - return watcher; -} - - -//////////// Emit Helpers ////////////////////////////////////////////////////////////////////////////////// -/** - * Helper method to initialize the event for {@link ImperativeEventEmitter.emitEvent} - * @param eventName The name of event to initialize - * @returns The initialized ImperativeEvent - */ -export function initEvent(eventName: string, parms: IEventSubscriptionParms ): ImperativeEvent { - this.ensureClassInitialized(); - return new ImperativeEvent({ - appName: this.appName, - eventName: eventName, - eventType: this.getEventType(eventName), - eventTime: new Date().toISOString(), - path: join(ProfileInfo.getZoweDir() ,this.getEventDir(parms.eventType, this.appName)), - isCustomShared: parms.isCustomShared, - logger: this.logger - }); -} - -/** - * Helper method to write contents out to disk for {@link ImperativeEventEmitter.emitEvent} - * @param location directory to write the file (i.e. emit the event) - * @param event the event to be written/emitted - * @internal We do not want developers writing events directly, they should use the `emit...` methods - */ -export function writeEvent(location: string, event: ImperativeEvent) { - const eventPath = join(location, (event.eventType).toString()); - const eventJson = { ...event.toJson(), loc: location }; - - this.ensureEventsDirExists(location); - fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); -} +import { Event } from "./Event"; //////////// Initialization Helpers ///////////////////////////////////////////////////////////////////////// /** - * Check to see if the Imperative Event Emitter instance has been initialized + * Check to see if the Event Emitter instance has been initialized */ export function ensureClassInitialized() { if (!this.initialized) { @@ -101,7 +33,7 @@ export function ensureClassInitialized() { } /** - * Check to see if the directory exists, otherwise, create it : ) + * Check to see if the directory exists, otherwise, create it * @param directoryPath Zowe or User dir where we will write the events */ export function ensureEventsDirExists(directoryPath: string) { @@ -115,7 +47,7 @@ export function ensureEventsDirExists(directoryPath: string) { } /** - * Check to see if the file path exists, otherwise, create it : ) + * Check to see if the file path exists, otherwise, create it * @param filePath Zowe or User path where we will write the events */ export function ensureEventFileExists(filePath: string) { @@ -129,55 +61,105 @@ export function ensureEventFileExists(filePath: string) { } /** - * Sets and returns the eventType based on eventName + * Returns the eventType based on eventName * @param eventName Name of event, ie: onSchemaChanged */ -export function getEventType(eventName: string): ImperativeEventTypes { - const isCustomShared = this.subscriptions.get(eventName).isCustomShared; - if (isCustomShared){ +export function getEventType(eventName: string): EventTypes { + const isCustomSharedEvent = this.subscriptions.get(eventName).isCustomSharedEvent; + if (isCustomSharedEvent){ // this.subscriptions.set(eventName, { // ...this.subscriptions.get(eventName), - // eventType: ImperativeCustomShared + // eventType: CustomShared // }); - return ImperativeCustomShared; + return CustomSharedEvents; } - if ( Object.values(ImperativeUserEvents).includes(eventName)){ + if ( Object.values(UserEvents).includes(eventName)){ // this.subscriptions.set(eventName, { // ...this.subscriptions.get(eventName), - // eventType: ImperativeUserEvents + // eventType: UserEvents // }); - return ImperativeUserEvents; + return UserEvents; } - if (Object.values(ImperativeSharedEvents).includes(eventName)){ + if (Object.values(SharedEvents).includes(eventName)){ // this.subscriptions.set(eventName, { // ...this.subscriptions.get(eventName), - // eventType: ImperativeSharedEvents + // eventType: SharedEvents // }); - return ImperativeSharedEvents; + return SharedEvents; } // this.subscriptions.set(eventName, { // ...this.subscriptions.get(eventName), - // eventType: ImperativeCustomUser + // eventType: CustomUser // }); - return ImperativeCustomUser; + return CustomUserEvents; } /** * Returns the directory path based on EventType * @param eventName Name of event, ie: onSchemaChanged - * @param eventType One of the ImperativeEventTypes from ImperativeEventConstants - * @param appName Needed for custom event path + * @param eventType One of the EventTypes from EventConstants + * @param appName Needed for custom event paths */ -export function getEventDir(eventType: ImperativeEventTypes, appName: string): string { +export function getEventDir(eventType: EventTypes, appName: string): string { switch (eventType) { - case ImperativeSharedEvents: + case SharedEvents: return join(ProfileInfo.getZoweDir(), ".events"); - case ImperativeCustomShared: + case CustomSharedEvents: return join(ProfileInfo.getZoweDir(),".events", appName); - case ImperativeCustomUser: + case CustomUserEvents: return join(dirname(ProfileInfo.getZoweDir()), ".events", appName); default: - //ImperativeUserEvents + //UserEvents return join(dirname(ProfileInfo.getZoweDir()), ".events"); } -} \ No newline at end of file +} + +//////////// Emit Helpers ////////////////////////////////////////////////////////////////////////////////// + +/** + * Helper method to write contents out to disk for {@link EventEmitter.emitEvent} + * @param filePath directory to write the file (i.e. emit the event) + * @param event the event to be written/emitted + * @internal We do not want developers writing events directly, they should use the `emit...` methods + */ +export function writeEvent(event: Event) { + const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); + const eventJson = { ...event.toJson(), eventFilePath: eventPath }; + + this.ensureEventsDirExists(eventPath); + fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); +} + +//////////// Subscription Helpers /////////////////////////////////////////////////////////////////////////// +/** + * Helper method to create watchers based on event strings and list of callbacks + * @param eventName name of event to which we will create a watcher for + * @param callbacks list of all callbacks for this watcher + * @returns The FSWatcher instance created + */ +export function setupWatcher(eventName: string): fs.FSWatcher { + const subscription = this.subscriptions.get(eventName); + const dir = join(ProfileInfo.getZoweDir(), subscription.dir); + this.ensureEventsDirExists(dir); + this.ensureEventFileExists(join(dir, eventName)); + + const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { + // Node.JS triggers this event 3 times + const eventContents = fs.readFileSync(dir).toString(); + const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IEventJson).time; + if (subscription.eventTime !== eventTime) { + callbacks.forEach(callback => callback()); + this.subscriptions.set(eventName, { + ...subscription, + eventTime: eventTime + }); + } + }); + // Update the map with the new watcher and callbacks + this.subscriptions.set(eventName, { + ...subscription, + watcher: watcher, + callbacks: [...subscription.callbacks, callbacks] + }); + return watcher; +} diff --git a/packages/imperative/src/events/src/ImperativeEvent.ts b/packages/imperative/src/events/src/ImperativeEvent.ts deleted file mode 100644 index dbd0268d08..0000000000 --- a/packages/imperative/src/events/src/ImperativeEvent.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* -* 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 { randomUUID } from "crypto"; -import { IImperativeEventJson, IImperativeEventParms } from "./doc"; -import { ImperativeEventTypes } from "./ImperativeEventConstants"; - -/** - * - * @export - * @class ImperativeEvent - */ -export class ImperativeEvent { - /** - * The name of event that occurred - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mEventName: string; - /** - * The application name that caused this event - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mAppName: string; - /** - * The type(ImperativeEventTypes) of event that occurred - * @private - * @type {string} - * @memberof ImperativeEventTypes - */ - private mEventType: string; - /** - * The time of the event created with new Date().toISOString() (ISO String) - * @private - * @type {string} - * @memberof ImperativeEvent - */ - private mEventTime: string; - - - - /** - * toString overload to be called automatically on string concatenation - * @returns string representation of the imperative event - */ - public toString = (): string => { - return `Type: ${this.eventType} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| ID: ${this.eventId}`; - }; - - /** - * toJson helper method to be called for emitting or logging imperative events - * @returns JSON representation of the imperative event - */ - public toJson = (): IImperativeEventJson => { - return { - time: this.eventTime, - name: this.eventName, - source: this.appName, - }; - }; - - constructor(parms: IImperativeEventParms) { - this.mEventTime = new Date().toISOString(); - this.mAppID = parms.appName; - parms.logger.debug("ImperativeEvent: " + this); - } - - public get eventTime(): string { - return this.mEventTime; - } - - public get eventName(): string { - return this.mEventName; - } - - public get appName(): string { - return this.mAppID; - } - - public get path(): string { - return this.mPath; - } -} diff --git a/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts b/packages/imperative/src/events/src/doc/IEventEmitterOpts.ts similarity index 80% rename from packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts rename to packages/imperative/src/events/src/doc/IEventEmitterOpts.ts index 2f9a20c223..c40aa89d1d 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventEmitterOpts.ts +++ b/packages/imperative/src/events/src/doc/IEventEmitterOpts.ts @@ -14,13 +14,13 @@ import { Logger } from "../../../logger"; /** * Imperative standard event emitter options * @export - * @interface IImperativeEventEmitterOpts + * @interface IEventEmitterOpts */ -export interface IImperativeEventEmitterOpts { +export interface IEventEmitterOpts { /** * The logger to use when logging the imperative event that occurred * @type {Logger} - * @memberof IImperativeEventEmitterOpts + * @memberof IEventEmitterOpts */ logger?: Logger; } diff --git a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts similarity index 65% rename from packages/imperative/src/events/src/doc/IImperativeEventJson.ts rename to packages/imperative/src/events/src/doc/IEventJson.ts index 5463874e4a..31d2c9b6b8 100644 --- a/packages/imperative/src/events/src/doc/IImperativeEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -9,40 +9,36 @@ * */ -import { ImperativeEventTypes } from "../ImperativeEventConstants"; +import { EventTypes } from "../EventConstants"; /** - * Imperative Event JSON representation + * Imperative Event JSON representation for user interpretation * @export - * @interface IImperativeEventJson + * @interface IEventJson */ -export interface IImperativeEventJson { +export interface IEventJson { /** * The time in which the event occurred */ - time: string; + eventTime: string; /** * The name of event that occurred */ - name: string; + eventName: string; /** * The type of event that occurred */ - type: ImperativeEventTypes; + eventType: EventTypes; /** * The application name that triggered the event */ - source: string; - /** - * The ID of the event that occurred - */ - id?: string; + appName: string; /** * The file path for information on the emitted event */ - loc?: string; + eventFilePath: string; /** * The indicator of user-specific (if true) or shared (if false) events */ - isCustomShared?: boolean; + isCustomSharedEvent: boolean; } diff --git a/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts b/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts index 4d299e1567..d58d10d7bb 100644 --- a/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts +++ b/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts @@ -9,7 +9,7 @@ * */ -import { ImperativeEventTypes } from "../ImperativeEventConstants"; +import { EventTypes } from "../EventConstants"; import { FSWatcher } from "fs"; /** @@ -20,10 +20,10 @@ import { FSWatcher } from "fs"; export interface IEventSubscriptionParms { /** * The type of event that occurred - * @type {ImperativeEventTypes} + * @type {EventTypes} * @memberof IEventSubscriptionParms */ - eventType?: ImperativeEventTypes + eventType?: EventTypes /** * The time of the latest event occurrence * @type {string} @@ -35,7 +35,7 @@ export interface IEventSubscriptionParms { * @type {boolean} * @memberof IEventSubscriptionParms */ - isCustomShared?: boolean + isCustomSharedEvent?: boolean /** * Event dir for the .event file * Incomplete dir path to be joined with the current value stored in zoweDir diff --git a/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts b/packages/imperative/src/events/src/doc/IRegisteredAction.ts similarity index 70% rename from packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts rename to packages/imperative/src/events/src/doc/IRegisteredAction.ts index 3f06b3f821..ba5384263b 100644 --- a/packages/imperative/src/events/src/doc/IImperativeRegisteredAction.ts +++ b/packages/imperative/src/events/src/doc/IRegisteredAction.ts @@ -10,14 +10,14 @@ */ /** - * Imperative Registered Action + * Imperative Registered Action (possibly change to IDisposableSubscription) * @export - * @interface IImperativeRegisteredAction + * @interface IRegisteredAction */ -export interface IImperativeRegisteredAction { +export interface IRegisteredAction { /** * The method to dispose of the registered action - * @memberof IImperativeRegisteredAction + * @memberof IRegisteredAction */ close(): void; } diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index 7516577fbb..ff2cfef6cc 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,7 +9,7 @@ * */ -export * from "./IImperativeEventEmitterOpts"; -export * from "./IImperativeRegisteredAction"; -export * from "./IImperativeEventJson"; +export * from "./IEventEmitterOpts"; +export * from "./IRegisteredAction"; +export * from "./IEventJson"; export * from "./IEventSubscriptionParms"; diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index ca66b4a64e..c0a83cbc53 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,7 +16,7 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { ImperativeEventEmitter, ImperativeSharedEvents } from "../../events"; +import { EventEmitter, SharedEvents } from "../../events"; /** * This class provides access to the known set of credential manager overrides @@ -133,7 +133,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventEmitter.instance.emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +188,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - ImperativeEventEmitter.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventEmitter.instance.emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From 32429fc73371e84e2d0b92bb8934a1a48e82cd8c Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 9 May 2024 13:59:22 -0400 Subject: [PATCH 12/72] finished Signed-off-by: Amber Torrise --- .../src/events/src/EEmovingStuff.ts | 134 -------------- .../imperative/src/events/src/EventEmitter.ts | 165 ++++++------------ .../src/events/src/EventEmitterManager.ts | 143 +++++++++++++++ .../src/events/src/EventUtilities.ts | 165 ------------------ .../src/events/src/doc/IEventEmitterOpts.ts | 26 --- .../src/events/src/doc/IEventJson.ts | 4 - .../events/src/doc/IEventSubscriptionParms.ts | 58 ------ .../imperative/src/events/src/doc/index.ts | 2 - 8 files changed, 199 insertions(+), 498 deletions(-) delete mode 100644 packages/imperative/src/events/src/EEmovingStuff.ts create mode 100644 packages/imperative/src/events/src/EventEmitterManager.ts delete mode 100644 packages/imperative/src/events/src/EventUtilities.ts delete mode 100644 packages/imperative/src/events/src/doc/IEventEmitterOpts.ts delete mode 100644 packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts diff --git a/packages/imperative/src/events/src/EEmovingStuff.ts b/packages/imperative/src/events/src/EEmovingStuff.ts deleted file mode 100644 index b17193b95f..0000000000 --- a/packages/imperative/src/events/src/EEmovingStuff.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* -* 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 { ImperativeError } from "../../error/src/ImperativeError"; -import { join, dirname } from "path"; -import { Logger } from "../../logger/src/Logger"; -import { IEventJson, IRegisteredAction } from "./doc"; -import { Event } from "./Event"; -import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; -import { ProfileInfo } from "../../config/src/ProfileInfo"; -import * as fs from "fs"; -import { eventNames } from "process"; - -/** - * The EventEmitter class manages event emissions and subscriptions for different applications. - * It utilizes a scoped singleton pattern where instances are mapped by application names. - * Each instance maintains its own set of event subscriptions, allowing for efficient management - * of event-related data. -*/ -export class EventEmitter { - private static instances: Map = new Map(); - private subscriptions: Map = new Map(); - public appName: string; - public logger: Logger; - - private constructor(appName: string, logger: Logger) { - this.subscriptions = new Map(); - this.appName = appName; - this.logger = logger; - } - - public static Helpers = { - getInstance: function(appName: string, logger?: Logger): EventEmitter { - if (!EventEmitter.instances.has(appName)) { - const effectiveLogger = logger ?? Logger.getImperativeLogger(); - const newInstance = new EventEmitter(appName, effectiveLogger); - this.instances.set(appName, newInstance); - } - return this.instances.get(appName); - }, - getEventDetails: function(eventName: string): IEventJson | undefined { - return this.subscriptions.get(eventName); - }, - ensureEventsDirExists: function(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } - }, - isUserEvent: function(eventName: string): boolean { - return Object.values(UserEvents).includes(eventName); - }, - isSharedEvent: function(eventName: string): boolean { - return Object.values(SharedEvents).includes(eventName); - }, - getEventDir: function(eventType: EventTypes, appName: string): string { - if (eventType == EventTypes.CustomSharedEvents || EventTypes.CustomUserEvents){ - return join(".events", appName); - } - return ".events"; - }, - createSubscription: function(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction{ - const dir = EventEmitter.Helpers.getEventDir(eventType, eeInst.appName); - EventEmitter.Helpers.ensureEventsDirExists(dir); - const filePath = join(dirname(ProfileInfo.getZoweDir()), eventName); - //possibly have to do some cleaning up of eventNames (custom ones might go crazy) - eeInst.subscriptions.set(eventName, { - eventTime: new Date().toISOString(), - eventName, - eventType, - appName: eeInst.appName, - eventFilePath: filePath - }); - // returns a disposable to automatically unsubscribe as cleanup when app closes - return { close: ()=> this.unsubscribe(eventName) }; - }, - writeEvent: function(event: Event) { - const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); - EventEmitter.Helpers.ensureEventsDirExists(eventPath); - fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); - } - }; - - public subscribeShared(eventName: string): void { - const isCustom = EventEmitter.Helpers.isSharedEvent(eventName); - const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; - const eeInst = EventEmitter.Helpers.getInstance(this.appName); - EventEmitter.Helpers.createSubscription(eeInst, eventName, eventType); - } - - public subscribeUser(eventName: string): void { - const isCustom = EventEmitter.Helpers.isUserEvent(eventName); - const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; - const eeInst = EventEmitter.Helpers.getInstance(this.appName); - EventEmitter.Helpers.createSubscription(eeInst, eventName, eventType); - } - - public emitEvent(eventName: string): void { - // search for correct event emitter instance based on appname. if there isnt one for that app, create it then search - // update the event file time for the event found via event.eventPath, write to the file - const event = EventEmitter.Helpers.getEventDetails(eventName); - if (!event) { - this.logger.error(`No subscription found for event: ${eventName}`); - return; - } - - const fullEvent = { - ...event, - eventTime: new Date().toISOString() // Update time to reflect the emission time - }; - EventEmitter.Helpers.writeEvent(); - this.logger.info(`Event emitted: ${JSON.stringify(fullEvent)}`); - } - - // * Intentional Unsubscription from given event - public unsubscribe(eventName: string): void { - if (this.subscriptions.has(eventName)) { - this.subscriptions.delete(eventName); - } else { - this.logger.error(`No subscription found for event: ${eventName}`); - } - } -} diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index 537bde4031..5ccf7f807f 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -8,144 +8,91 @@ * Copyright Contributors to the Zowe Project. * */ - import { Logger } from "../../logger/src/Logger"; -import { - IRegisteredAction, - IEventEmitterOpts, - IEventSubscriptionParms -} from "./doc"; +import { EventEmitterManager } from "./EventEmitterManager"; +import { EventTypes } from "./EventConstants"; import { ImperativeError } from "../../error/src/ImperativeError"; -import * as Utilities from "./EventUtilities"; -import { ConfigUtils } from "../../config/src/ConfigUtils"; -import { LoggerManager } from "../../logger/src/LoggerManager"; -import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; -import { unsubscribe } from "diagnostics_channel"; +import { Event } from "./Event"; /** -*The EventEmitter class is a scoped singleton class maintained by a maps of instances based on -*application names as keys that keep track of event subscriptions(stored event properties) -*until subscription removal. -* -*Subscription model: event metadata is stored in a map upon subscription. -*Upon emission, the event is looked up and the event time is updated to the current time -*to reflect this new event. The time change can be used to implement fileWatcher -*callback functions. -*/ - + * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. + * It maintains a map of subscriptions where each event name is associated with its corresponding Event object. + * + * @export + * @class EventEmitter + */ export class EventEmitter { - private static mInstance: EventEmitter; - private static initialized = false; - private subscriptions: Map< string, IEventSubscriptionParms> = new Map(); + public subscriptions: Map = new Map(); public appName: string; public logger: Logger; /** - /* Check to see if the Event Emitter instance has been initialized - */ - public static initialize(appName?: string, options?: IEventEmitterOpts) { - if (this.initialized) { - throw new ImperativeError({msg: "Only one instance of the Imperative Event Emitter is allowed"}); - } - this.initialized = true; - - if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { - ConfigUtils.initImpUtils("zowe"); - } - - EventEmitter.instance.appName = appName; - EventEmitter.instance.logger = options?.logger ?? Logger.getImperativeLogger(); - } - - public static get instance(): EventEmitter { - if (this.mInstance == null) { - this.mInstance = new EventEmitter(); - this.mInstance.subscriptions = new Map(); - } - return this.mInstance; + * Creates an instance of EventEmitter. + * + * @param {string} appName The name of the application this emitter is associated with. + * @param {Logger} logger The logger instance used for logging information and errors. + */ + public constructor(appName: string, logger: Logger) { + this.subscriptions = new Map(); + this.appName = appName; + this.logger = logger; } /** - * Check to see if the Imperative Event Emitter instance has been initialized + * Utility helpers from EventEmitterManager for managing events. */ - private ensureClassInitialized() { - if (!EventEmitter.initialized) { - throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); - } - } + public utils = EventEmitterManager.Helpers; - private initEvent(eventName: string): Event { - this.ensureClassInitialized(); - return new Event({ - appName: this.appName, - eventName, - this.isCustomSharedEvent(eventName), - logger: this.logger - }); + /** + * Subscribes to a shared event. This method determines the event type and creates a subscription. + * + * @param {string} eventName + */ + public subscribeShared(eventName: string): void { + const isCustom = this.utils.isSharedEvent(eventName); + const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; + this.utils.createSubscription(this, eventName, eventType); } - /** - * Simple method to write the events to disk - * @param eventName The name of event to write - * @internal We do not want to make this function accessible to any application developers + * Subscribes to a user event. This method determines whether the event is custom and creates a subscription accordingly. + * + * @param {string} eventName */ - public emitEvent(eventName: string) { - const event: Event = this.initEvent(eventName); - Utilities.writeEvent(event); + public subscribeUser(eventName: string): void { + const isCustom = this.utils.isUserEvent(eventName); + const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; + this.utils.createSubscription(this, eventName, eventType); } - /** - * Method to register your custom actions to a given event emission - * @param eventName name of event to register custom action to - * @param eventParms passed along parms to contribute to the overall event subscription data object + * Emits an event by updating the event time and writing the event data to the associated event file. + * This method throws an error if the event cannot be written. + * + * @param {string} eventName + * @throws {ImperativeError} */ - public subscribe(eventName: string, eventParms: IEventSubscriptionParms): IRegisteredAction { - // HELP - why are we returning subscription.watcher.close? - Utilities.ensureClassInitialized(); - const subscription = this.subscriptions.get(eventName); - if (subscription != null){ - // modify existing subscription - if (subscription.watcher != null){ - const watcherToClose = subscription.watcher; - watcherToClose.removeAllListeners(eventName).close(); - } - this.subscriptions.set(eventName, { - ...subscription, - //HELP - not sure if we should keep old callbacks? or overwrite with new: - callbacks: [...subscription.callbacks, ...eventParms.callbacks], - watcher: Utilities.setupWatcher(eventName), - isCustomSharedEvent: eventParms.isCustomSharedEvent, - eventTime: new Date().toISOString(), - }); - } else { - // create new subscription - this.subscriptions.set(eventName, { - callbacks: eventParms.callbacks, //callback has to be set before watcher - watcher: Utilities.setupWatcher(eventName), - eventType: Utilities.getEventType(eventName), - isCustomSharedEvent: eventParms.isCustomSharedEvent, - eventTime: new Date().toISOString(), - dir: Utilities.getEventDir(Utilities.getEventType(eventName), this.appName) - }); + public emitEvent(eventName: string): void { + try { + const event = this.subscriptions.get(eventName); + event.eventTime = new Date().toISOString(); + this.utils.writeEvent(event); + } catch (err) { + throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); } - // returns a disposable to automatically unsubscribe as cleanup when app closes - return { close: ()=> this.unsubscribe(eventName) }; } /** - * Unsubscribe from given event - * @param eventName name of event + * Unsubscribes from a given event by removing it from the subscriptions map. + * Logs an error if the event name is not found in the subscriptions. + * + * @param {string} eventName */ public unsubscribe(eventName: string): void { - Utilities.ensureClassInitialized(); if (this.subscriptions.has(eventName)) { - this.subscriptions.get(eventName).watcher.removeAllListeners(eventName).close(); this.subscriptions.delete(eventName); + } else { + this.logger.error(`No subscription found for event: ${eventName}`); } } -} - -// HELP -create an unsubscribe from all events for a given app? -// - do this by deleting eventEmitter instance and all its associated files? \ No newline at end of file +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts new file mode 100644 index 0000000000..5ab07e519d --- /dev/null +++ b/packages/imperative/src/events/src/EventEmitterManager.ts @@ -0,0 +1,143 @@ +/* +* 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 { Logger } from "../../logger/src/Logger"; +import { EventEmitter } from "./EventEmitter"; +import { ImperativeError } from "../../error/src/ImperativeError"; +import { dirname, join } from "path"; +import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; +import * as fs from "fs"; +import { ProfileInfo } from "../../config"; +import { IRegisteredAction } from "./doc"; +import { Event } from "./Event"; + +/** + * The EventEmitterManager class serves as a central hub for managing + * event emitters and their app-specific-subscriptions. + * + * @export + * @class EventEmitterManager + */ +export class EventEmitterManager { + private static instances: Map = new Map(); + private static logger: Logger; //TO DO: MAKE A CONFIGURABLE LOGGER + + /** + * Retrieves an existing EventEmitter instance or creates a new one if it does not exist. + * Ensures that each application has a unique EventEmitter instance. + * + * @static + * @param {string} appName key to KVP for managed event emitter instances + * @return {(EventEmitter | undefined)} Returns the EventEmitter instance or undefined if it cannot be created. + */ + public static getEmitter(appName: string): EventEmitter | undefined { + if (!this.instances.has(appName)) { + const newInstance = new EventEmitter(appName, this.logger); + this.instances.set(appName, newInstance); + } + return this.instances.get(appName); + } + + /** + * A collection of helper functions related to event management, including: + * - directory management, + * - event type determination + * - subscription creation + * - event writing + */ + public static Helpers = { + /** + * Ensures that the specified directory for storing event files exists. + * Creates the directory if not. + * + * @param {string} directoryPath + */ + ensureEventsDirExists: function(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + }, + + /** + * Determines if the specified event name is a user event. + * + * @param {string} eventName + * @return {boolean} + */ + isUserEvent: function(eventName: string): boolean { + return Object.values(UserEvents).includes(eventName); + }, + + /** + * Determines if the specified event name is a shared event. + * + * @param {string} eventName + * @return {boolean} + */ + isSharedEvent: function(eventName: string): boolean { + return Object.values(SharedEvents).includes(eventName); + }, + + /** + * Retrieves the directory path for events based on the event type and application name. + * + * @param {EventTypes} eventType + * @param {string} appName + * @return {string} + */ + getEventDir: function(eventType: EventTypes, appName: string): string { + return eventType === EventTypes.CustomSharedEvents || eventType === EventTypes.CustomUserEvents ? + join(".events", appName) : ".events"; + }, + + /** + * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. + * + * @param {EventEmitter} eeInst The instance of EventEmitter to which the event is registered. + * @param {string} eventName + * @param {EventTypes} eventType + * @return {IRegisteredAction} An object that includes a method to unsubscribe from the event. + */ + createSubscription: function(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction { + const dir = this.getEventDir(eventType, eeInst.appName); + this.ensureEventsDirExists(dir); + const filePath = join(dirname(ProfileInfo.getZoweDir()), eventName); + + const newEvent = new Event({ + eventTime: new Date().toISOString(), + eventName: eventName, + eventType: eventType, + appName: eeInst.appName, + eventFilePath: filePath + }); + + eeInst.subscriptions.set(eventName, newEvent); + + return { + close: () => eeInst.unsubscribe(eventName) + }; + }, + + /** + * Writes the specified event to its corresponding file. + * + * @param {Event} event + */ + writeEvent: function(event: Event) { + const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); + this.ensureEventsDirExists(eventPath); + fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); + } + }; +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventUtilities.ts b/packages/imperative/src/events/src/EventUtilities.ts deleted file mode 100644 index 643b1b484b..0000000000 --- a/packages/imperative/src/events/src/EventUtilities.ts +++ /dev/null @@ -1,165 +0,0 @@ -// move over the following methods -// getEventDir -// getEventType -// ensureEventFileExists -// ensureEventsDirExists -// ensureClassInitialized -// setupWatcher -// initEvent -// writeEvent - -import * as fs from "fs"; -import { join, dirname } from "path"; -import { ImperativeError } from "../../error/src/ImperativeError"; -import { - UserEvents, - SharedEvents, - CustomSharedEvents, - CustomUserEvents, - EventTypes -} from "./EventConstants"; -import { IEventJson, IEventSubscriptionParms } from "./doc"; -import { ProfileInfo } from "../../config"; -import { Event } from "./Event"; - -//////////// Initialization Helpers ///////////////////////////////////////////////////////////////////////// -/** - * Check to see if the Event Emitter instance has been initialized - */ -export function ensureClassInitialized() { - if (!this.initialized) { - throw new ImperativeError({msg: "You must initialize the instance before using any of its methods."}); - } -} - -/** - * Check to see if the directory exists, otherwise, create it - * @param directoryPath Zowe or User dir where we will write the events - */ -export function ensureEventsDirExists(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } -} - -/** - * Check to see if the file path exists, otherwise, create it - * @param filePath Zowe or User path where we will write the events - */ -export function ensureEventFileExists(filePath: string) { - try { - if (!fs.existsSync(filePath)) { - fs.closeSync(fs.openSync(filePath, 'w')); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); - } -} - -/** - * Returns the eventType based on eventName - * @param eventName Name of event, ie: onSchemaChanged - */ -export function getEventType(eventName: string): EventTypes { - const isCustomSharedEvent = this.subscriptions.get(eventName).isCustomSharedEvent; - if (isCustomSharedEvent){ - // this.subscriptions.set(eventName, { - // ...this.subscriptions.get(eventName), - // eventType: CustomShared - // }); - return CustomSharedEvents; - } - if ( Object.values(UserEvents).includes(eventName)){ - // this.subscriptions.set(eventName, { - // ...this.subscriptions.get(eventName), - // eventType: UserEvents - // }); - return UserEvents; - } - if (Object.values(SharedEvents).includes(eventName)){ - // this.subscriptions.set(eventName, { - // ...this.subscriptions.get(eventName), - // eventType: SharedEvents - // }); - return SharedEvents; - } - // this.subscriptions.set(eventName, { - // ...this.subscriptions.get(eventName), - // eventType: CustomUser - // }); - return CustomUserEvents; -} - -/** - * Returns the directory path based on EventType - * @param eventName Name of event, ie: onSchemaChanged - * @param eventType One of the EventTypes from EventConstants - * @param appName Needed for custom event paths - */ -export function getEventDir(eventType: EventTypes, appName: string): string { - switch (eventType) { - case SharedEvents: - return join(ProfileInfo.getZoweDir(), ".events"); - case CustomSharedEvents: - return join(ProfileInfo.getZoweDir(),".events", appName); - case CustomUserEvents: - return join(dirname(ProfileInfo.getZoweDir()), ".events", appName); - default: - //UserEvents - return join(dirname(ProfileInfo.getZoweDir()), ".events"); - } -} - -//////////// Emit Helpers ////////////////////////////////////////////////////////////////////////////////// - -/** - * Helper method to write contents out to disk for {@link EventEmitter.emitEvent} - * @param filePath directory to write the file (i.e. emit the event) - * @param event the event to be written/emitted - * @internal We do not want developers writing events directly, they should use the `emit...` methods - */ -export function writeEvent(event: Event) { - const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); - const eventJson = { ...event.toJson(), eventFilePath: eventPath }; - - this.ensureEventsDirExists(eventPath); - fs.writeFileSync(eventPath, JSON.stringify(eventJson, null, 2)); -} - -//////////// Subscription Helpers /////////////////////////////////////////////////////////////////////////// -/** - * Helper method to create watchers based on event strings and list of callbacks - * @param eventName name of event to which we will create a watcher for - * @param callbacks list of all callbacks for this watcher - * @returns The FSWatcher instance created - */ -export function setupWatcher(eventName: string): fs.FSWatcher { - const subscription = this.subscriptions.get(eventName); - const dir = join(ProfileInfo.getZoweDir(), subscription.dir); - this.ensureEventsDirExists(dir); - this.ensureEventFileExists(join(dir, eventName)); - - const watcher = fs.watch(join(dir, eventName), (event: "rename" | "change") => { - // Node.JS triggers this event 3 times - const eventContents = fs.readFileSync(dir).toString(); - const eventTime = eventContents.length === 0 ? "" : (JSON.parse(eventContents) as IEventJson).time; - if (subscription.eventTime !== eventTime) { - callbacks.forEach(callback => callback()); - this.subscriptions.set(eventName, { - ...subscription, - eventTime: eventTime - }); - } - }); - // Update the map with the new watcher and callbacks - this.subscriptions.set(eventName, { - ...subscription, - watcher: watcher, - callbacks: [...subscription.callbacks, callbacks] - }); - return watcher; -} diff --git a/packages/imperative/src/events/src/doc/IEventEmitterOpts.ts b/packages/imperative/src/events/src/doc/IEventEmitterOpts.ts deleted file mode 100644 index c40aa89d1d..0000000000 --- a/packages/imperative/src/events/src/doc/IEventEmitterOpts.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* -* 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 { Logger } from "../../../logger"; - -/** - * Imperative standard event emitter options - * @export - * @interface IEventEmitterOpts - */ -export interface IEventEmitterOpts { - /** - * The logger to use when logging the imperative event that occurred - * @type {Logger} - * @memberof IEventEmitterOpts - */ - logger?: Logger; -} diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 31d2c9b6b8..2b21d98612 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -37,8 +37,4 @@ export interface IEventJson { * The file path for information on the emitted event */ eventFilePath: string; - /** - * The indicator of user-specific (if true) or shared (if false) events - */ - isCustomSharedEvent: boolean; } diff --git a/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts b/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts deleted file mode 100644 index d58d10d7bb..0000000000 --- a/packages/imperative/src/events/src/doc/IEventSubscriptionParms.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* -* 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 { EventTypes } from "../EventConstants"; -import { FSWatcher } from "fs"; - -/** - * Imperative Registered Action - * @export - * @interface IEventSubscriptionParms - */ -export interface IEventSubscriptionParms { - /** - * The type of event that occurred - * @type {EventTypes} - * @memberof IEventSubscriptionParms - */ - eventType?: EventTypes - /** - * The time of the latest event occurrence - * @type {string} - * @memberof IEventSubscriptionParms - */ - eventTime?: string - /** - * Specifies whether this is a custom shared event, necessary for extenders to set - * @type {boolean} - * @memberof IEventSubscriptionParms - */ - isCustomSharedEvent?: boolean - /** - * Event dir for the .event file - * Incomplete dir path to be joined with the current value stored in zoweDir - * @type {string} - * @memberof IEventSubscriptionParms - */ - dir?: string; - /** - * The attached watcher for this subscription - * @type {FSWatcher} - * @memberof IEventSubscriptionParms - */ - watcher?: FSWatcher - /** - * Functions to trigger upon event emission - * @type {Function[]} - * @memberof IEventSubscriptionParms - */ - callbacks?: Function[] -} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index ff2cfef6cc..35c76f9b31 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,7 +9,5 @@ * */ -export * from "./IEventEmitterOpts"; export * from "./IRegisteredAction"; export * from "./IEventJson"; -export * from "./IEventSubscriptionParms"; From af0933d80c3a377f11ce978568771a18adf51597 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 9 May 2024 14:02:14 -0400 Subject: [PATCH 13/72] unsure about keeping the name Registered Action Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/doc/IRegisteredAction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/doc/IRegisteredAction.ts b/packages/imperative/src/events/src/doc/IRegisteredAction.ts index ba5384263b..0cf5156b1e 100644 --- a/packages/imperative/src/events/src/doc/IRegisteredAction.ts +++ b/packages/imperative/src/events/src/doc/IRegisteredAction.ts @@ -10,7 +10,7 @@ */ /** - * Imperative Registered Action (possibly change to IDisposableSubscription) + * Registered Action (possibly change to IDisposableSubscription) * @export * @interface IRegisteredAction */ From 5f3ba1685dbeea4de80f33f9f13412685a728db1 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 9 May 2024 16:43:22 -0400 Subject: [PATCH 14/72] adding in watcher setup with fernando Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/Event.ts | 11 ++-- .../imperative/src/events/src/EventEmitter.ts | 36 +++++++---- .../src/events/src/EventEmitterManager.ts | 62 ++++++++++++------- .../src/events/src/doc/IEventJson.ts | 9 ++- 4 files changed, 78 insertions(+), 40 deletions(-) diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index 4063f8bf95..d131eb6dfa 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -1,3 +1,4 @@ +import { FSWatcher } from "fs"; import { EventTypes } from "./EventConstants"; import { IEventJson } from "./doc"; @@ -6,14 +7,16 @@ export class Event implements IEventJson { eventName: string; eventType: EventTypes; appName: string; - eventFilePath: string; + filePath: string; + watchers: FSWatcher[]; - constructor({ eventTime, eventName, eventType, appName, eventFilePath }: IEventJson) { + constructor({ eventTime, eventName, eventType, appName, filePath: eventFilePath, watchers}: IEventJson) { this.eventTime = eventTime; this.eventName = eventName; this.eventType = eventType; this.appName = appName; - this.eventFilePath = eventFilePath; + this.filePath = eventFilePath; + this.watchers = watchers; } public toJson() { @@ -22,7 +25,7 @@ export class Event implements IEventJson { eventName: this.eventName, eventType: this.eventType, appName: this.appName, - eventFilePath: this.eventFilePath + eventFilePath: this.filePath }; } } \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index 5ccf7f807f..42f1b10395 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -1,6 +1,6 @@ /* * 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 +`* 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 @@ -13,6 +13,9 @@ import { EventEmitterManager } from "./EventEmitterManager"; import { EventTypes } from "./EventConstants"; import { ImperativeError } from "../../error/src/ImperativeError"; import { Event } from "./Event"; +import { ConfigUtils } from "../../config"; +import { LoggerManager } from "../../logger/src/LoggerManager"; +import { ImperativeConfig } from "../../utilities"; /** * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. @@ -22,7 +25,8 @@ import { Event } from "./Event"; * @class EventEmitter */ export class EventEmitter { - public subscriptions: Map = new Map(); + public events: Map = new Map(); + public eventTimes: Map; public appName: string; public logger: Logger; @@ -32,10 +36,15 @@ export class EventEmitter { * @param {string} appName The name of the application this emitter is associated with. * @param {Logger} logger The logger instance used for logging information and errors. */ - public constructor(appName: string, logger: Logger) { - this.subscriptions = new Map(); + public constructor(appName: string, logger?: Logger) { + this.events = new Map(); this.appName = appName; - this.logger = logger; + + // Ensure we have correct environmental conditions to setup logger + if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { + ConfigUtils.initImpUtils("zowe"); + } + this.logger = logger ?? Logger.getImperativeLogger(); } /** @@ -59,10 +68,11 @@ export class EventEmitter { * * @param {string} eventName */ - public subscribeUser(eventName: string): void { + public subscribeUser(eventName: string, callbacks: Function[]): void { const isCustom = this.utils.isUserEvent(eventName); const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; this.utils.createSubscription(this, eventName, eventType); + this.utils.setupWatcher(this, eventName, callbacks); } /** @@ -74,7 +84,7 @@ export class EventEmitter { */ public emitEvent(eventName: string): void { try { - const event = this.subscriptions.get(eventName); + const event = this.events.get(eventName); event.eventTime = new Date().toISOString(); this.utils.writeEvent(event); } catch (err) { @@ -89,10 +99,14 @@ export class EventEmitter { * @param {string} eventName */ public unsubscribe(eventName: string): void { - if (this.subscriptions.has(eventName)) { - this.subscriptions.delete(eventName); - } else { - this.logger.error(`No subscription found for event: ${eventName}`); + try{ + // find watcher list and close everything + this.events.get(eventName).watchers.forEach((watcher)=>{ + watcher.removeAllListeners(eventName).close(); + }); + this.events.delete(eventName); + } catch(err){ + throw new ImperativeError({ msg: `Error unsubscribing from event: ${eventName}`, causeErrors: err }); } } } \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts index 5ab07e519d..c3848ce89b 100644 --- a/packages/imperative/src/events/src/EventEmitterManager.ts +++ b/packages/imperative/src/events/src/EventEmitterManager.ts @@ -8,7 +8,6 @@ * Copyright Contributors to the Zowe Project. */ -import { Logger } from "../../logger/src/Logger"; import { EventEmitter } from "./EventEmitter"; import { ImperativeError } from "../../error/src/ImperativeError"; import { dirname, join } from "path"; @@ -20,14 +19,13 @@ import { Event } from "./Event"; /** * The EventEmitterManager class serves as a central hub for managing - * event emitters and their app-specific-subscriptions. + * event emitters and their watched events. * * @export * @class EventEmitterManager */ export class EventEmitterManager { private static instances: Map = new Map(); - private static logger: Logger; //TO DO: MAKE A CONFIGURABLE LOGGER /** * Retrieves an existing EventEmitter instance or creates a new one if it does not exist. @@ -47,28 +45,13 @@ export class EventEmitterManager { /** * A collection of helper functions related to event management, including: - * - directory management, + * - directory management * - event type determination * - subscription creation + * - watcher creation and setting callbacks * - event writing */ public static Helpers = { - /** - * Ensures that the specified directory for storing event files exists. - * Creates the directory if not. - * - * @param {string} directoryPath - */ - ensureEventsDirExists: function(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } - }, - /** * Determines if the specified event name is a user event. * @@ -101,6 +84,22 @@ export class EventEmitterManager { join(".events", appName) : ".events"; }, + /** + * Ensures that the specified directory for storing event files exists. + * Creates the directory if not. + * + * @param {string} directoryPath + */ + ensureEventsDirExists: function(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + }, + /** * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. * @@ -119,23 +118,40 @@ export class EventEmitterManager { eventName: eventName, eventType: eventType, appName: eeInst.appName, - eventFilePath: filePath + filePath: filePath, + watchers: [] }); - eeInst.subscriptions.set(eventName, newEvent); + eeInst.events.set(eventName, newEvent); return { close: () => eeInst.unsubscribe(eventName) }; }, + setupWatcher: function(eeInst: EventEmitter, eventName: string, callbacks: Function[] = []): fs.FSWatcher { + const event = eeInst.events.get(eventName); + const watcher = fs.watch(event.filePath, (trigger: "rename" | "change") => { + // Accommodates for the delay between actual file change event and fsWatcher's perception + //(Node.JS triggers this notification event 3 times) + if (eeInst.eventTimes.get(eventName) !== event.eventTime) { + eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); + // Promise.all(callbacks) + callbacks.forEach(cb => cb()); + eeInst.eventTimes.set(eventName, event.eventTime); + } + }); + event.watchers.push(watcher); + return watcher; + }, + /** * Writes the specified event to its corresponding file. * * @param {Event} event */ writeEvent: function(event: Event) { - const eventPath = join(ProfileInfo.getZoweDir(), event.eventFilePath); + const eventPath = join(ProfileInfo.getZoweDir(), event.filePath); this.ensureEventsDirExists(eventPath); fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); } diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 2b21d98612..1e3904127e 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -10,6 +10,7 @@ */ import { EventTypes } from "../EventConstants"; +import * as fs from "fs"; /** * Imperative Event JSON representation for user interpretation @@ -36,5 +37,9 @@ export interface IEventJson { /** * The file path for information on the emitted event */ - eventFilePath: string; -} + filePath: string; + /** + * List of watchers to eventually close + */ + watchers: fs.FSWatcher[]; +} \ No newline at end of file From e7d0fb28c189a9d81660730e136ec141fd73ced7 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 9 May 2024 17:16:00 -0400 Subject: [PATCH 15/72] fixing build errors. need to fix custom logger passing Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 4 ++-- .../src/config/src/api/ConfigSecure.ts | 2 +- packages/imperative/src/events/src/Event.ts | 17 ++++++++++++++--- .../imperative/src/events/src/EventEmitter.ts | 13 ++++++++----- .../src/events/src/EventEmitterManager.ts | 10 +++++----- .../imperative/src/events/src/doc/IEventJson.ts | 2 +- .../security/src/CredentialManagerOverride.ts | 4 ++-- 7 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 113f31c640..64146412e0 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,8 +31,8 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; -import { EventEmitter } from "../../events"; import { Logger } from "../../logger"; +import { EventEmitter } from "../../events/src/EventEmitter"; /** * Enum used by Config class to maintain order of config layers @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - EventEmitter.instance.initialize(app, { logger:Logger.getAppLogger() }); + new EventEmitter(app, Logger.getAppLogger()); // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 9d5fdc0517..40e91ba14f 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -132,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - EventEmitter.instance.emitEvent(UserEvents.ON_VAULT_CHANGED); + new EventEmitter("Zowe").emitEvent(UserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index d131eb6dfa..fe00c4c168 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -1,3 +1,14 @@ +/* +* 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 { FSWatcher } from "fs"; import { EventTypes } from "./EventConstants"; import { IEventJson } from "./doc"; @@ -8,15 +19,15 @@ export class Event implements IEventJson { eventType: EventTypes; appName: string; filePath: string; - watchers: FSWatcher[]; + subscriptions: FSWatcher[]; - constructor({ eventTime, eventName, eventType, appName, filePath: eventFilePath, watchers}: IEventJson) { + constructor({ eventTime, eventName, eventType, appName, filePath: eventFilePath, subscriptions}: IEventJson) { this.eventTime = eventTime; this.eventName = eventName; this.eventType = eventType; this.appName = appName; this.filePath = eventFilePath; - this.watchers = watchers; + this.subscriptions = subscriptions; } public toJson() { diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index 42f1b10395..96f1eeefdd 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -1,6 +1,6 @@ /* * 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 +* 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 @@ -8,6 +8,7 @@ * Copyright Contributors to the Zowe Project. * */ + import { Logger } from "../../logger/src/Logger"; import { EventEmitterManager } from "./EventEmitterManager"; import { EventTypes } from "./EventConstants"; @@ -40,7 +41,8 @@ export class EventEmitter { this.events = new Map(); this.appName = appName; - // Ensure we have correct environmental conditions to setup logger + // Ensure we have correct environmental conditions to setup a custom logger, + // otherwise use default logger if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { ConfigUtils.initImpUtils("zowe"); } @@ -93,15 +95,16 @@ export class EventEmitter { } /** - * Unsubscribes from a given event by removing it from the subscriptions map. - * Logs an error if the event name is not found in the subscriptions. + * Unsubscribes from a given event by closing the file watchers associated with that event THEN + * deleting that event from the EventEmitter's events map. + * Logs an error if eventName isn't found. * * @param {string} eventName */ public unsubscribe(eventName: string): void { try{ // find watcher list and close everything - this.events.get(eventName).watchers.forEach((watcher)=>{ + this.events.get(eventName).subscriptions.forEach((watcher)=>{ watcher.removeAllListeners(eventName).close(); }); this.events.delete(eventName); diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts index c3848ce89b..55b149d338 100644 --- a/packages/imperative/src/events/src/EventEmitterManager.ts +++ b/packages/imperative/src/events/src/EventEmitterManager.ts @@ -6,6 +6,7 @@ * SPDX-License-Identifier: EPL-2.0 * * Copyright Contributors to the Zowe Project. +* */ import { EventEmitter } from "./EventEmitter"; @@ -37,7 +38,7 @@ export class EventEmitterManager { */ public static getEmitter(appName: string): EventEmitter | undefined { if (!this.instances.has(appName)) { - const newInstance = new EventEmitter(appName, this.logger); + const newInstance = new EventEmitter(appName); this.instances.set(appName, newInstance); } return this.instances.get(appName); @@ -47,8 +48,7 @@ export class EventEmitterManager { * A collection of helper functions related to event management, including: * - directory management * - event type determination - * - subscription creation - * - watcher creation and setting callbacks + * - subscription creation and setting callbacks * - event writing */ public static Helpers = { @@ -119,7 +119,7 @@ export class EventEmitterManager { eventType: eventType, appName: eeInst.appName, filePath: filePath, - watchers: [] + subscriptions: [] }); eeInst.events.set(eventName, newEvent); @@ -141,7 +141,7 @@ export class EventEmitterManager { eeInst.eventTimes.set(eventName, event.eventTime); } }); - event.watchers.push(watcher); + event.subscriptions.push(watcher); return watcher; }, diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 1e3904127e..06036fec15 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -41,5 +41,5 @@ export interface IEventJson { /** * List of watchers to eventually close */ - watchers: fs.FSWatcher[]; + subscriptions: fs.FSWatcher[]; } \ No newline at end of file diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index c0a83cbc53..1afc159a72 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -133,7 +133,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventEmitter.instance.emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + new EventEmitter("Zowe").emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +188,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventEmitter.instance.emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + new EventEmitter("Zowe").emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From 423455bb3db6a9153be54a55761474b2fce96b38 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 13 May 2024 14:22:05 -0400 Subject: [PATCH 16/72] resolving multi-level circular dependency issues. also adding new classes to index for exporting Signed-off-by: Amber Torrise --- .../imperative/src/config/src/ConfigUtils.ts | 16 +++ .../imperative/src/config/src/ProfileInfo.ts | 11 +- .../src/config/src/api/ConfigSecure.ts | 4 +- packages/imperative/src/events/index.ts | 2 + .../imperative/src/events/src/EventEmitter.ts | 17 ++- .../src/events/src/EventEmitterManager.ts | 120 ---------------- .../imperative/src/events/src/EventUtils.ts | 133 ++++++++++++++++++ .../security/src/CredentialManagerOverride.ts | 7 +- 8 files changed, 167 insertions(+), 143 deletions(-) create mode 100644 packages/imperative/src/events/src/EventUtils.ts diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 583676a0f8..d2536671af 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -23,6 +23,22 @@ import { Logger } from "../../logger/src/Logger"; import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; export class ConfigUtils { + /** + * Retrieves the Zowe CLI home directory. In the situation Imperative has + * not initialized it we use a default value. + * @returns {string} - Returns the Zowe home directory + */ + public static getZoweDir(): string { + const defaultHome = pathJoin(osHomedir(), ".zowe"); + if (ImperativeConfig.instance.loadedConfig?.defaultHome !== defaultHome) { + ImperativeConfig.instance.loadedConfig = { + name: "zowe", + defaultHome, + envVariablePrefix: "ZOWE" + }; + } + return ImperativeConfig.instance.cliHome; + } /** * Coerces string property value to a boolean or number type. * @param value String value diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 278524d0d4..06c2e795db 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -730,17 +730,10 @@ export class ProfileInfo { * Retrieves the Zowe CLI home directory. In the situation Imperative has * not initialized it we use a default value. * @returns {string} - Returns the Zowe home directory + * @deprecated Use ConfigUtils.getZoweDir() */ public static getZoweDir(): string { - const defaultHome = path.join(os.homedir(), ".zowe"); - if (ImperativeConfig.instance.loadedConfig?.defaultHome !== defaultHome) { - ImperativeConfig.instance.loadedConfig = { - name: "zowe", - defaultHome, - envVariablePrefix: "ZOWE" - }; - } - return ImperativeConfig.instance.cliHome; + return ConfigUtils.getZoweDir(); } /** diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 40e91ba14f..0bfc373477 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,8 +20,8 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; -import { EventEmitter } from "../../../events/src/EventEmitter"; import { UserEvents } from "../../../events/src/EventConstants"; +import { EventEmitterManager } from "../../../events/src/EventEmitterManager"; /** * API Class for manipulating config layers. @@ -132,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - new EventEmitter("Zowe").emitEvent(UserEvents.ON_VAULT_CHANGED); + EventEmitterManager.getEmitter('Zowe').emitEvent(UserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index d680f41f56..06e8f18aa5 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -13,3 +13,5 @@ export * from "./src/doc"; export * from "./src/Event"; export * from "./src/EventConstants"; export * from "./src/EventEmitter"; +export * from "./src/EventEmitterManager"; +export * from "./src/EventUtils"; \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index 96f1eeefdd..e73fe4fdce 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -10,13 +10,13 @@ */ import { Logger } from "../../logger/src/Logger"; -import { EventEmitterManager } from "./EventEmitterManager"; import { EventTypes } from "./EventConstants"; import { ImperativeError } from "../../error/src/ImperativeError"; import { Event } from "./Event"; -import { ConfigUtils } from "../../config"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { ImperativeConfig } from "../../utilities"; +import { EventUtils } from "./EventUtils"; /** * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. @@ -52,7 +52,6 @@ export class EventEmitter { /** * Utility helpers from EventEmitterManager for managing events. */ - public utils = EventEmitterManager.Helpers; /** * Subscribes to a shared event. This method determines the event type and creates a subscription. @@ -60,9 +59,9 @@ export class EventEmitter { * @param {string} eventName */ public subscribeShared(eventName: string): void { - const isCustom = this.utils.isSharedEvent(eventName); + const isCustom = EventUtils.isSharedEvent(eventName); const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; - this.utils.createSubscription(this, eventName, eventType); + EventUtils.createSubscription(this, eventName, eventType); } /** @@ -71,10 +70,10 @@ export class EventEmitter { * @param {string} eventName */ public subscribeUser(eventName: string, callbacks: Function[]): void { - const isCustom = this.utils.isUserEvent(eventName); + const isCustom = EventUtils.isUserEvent(eventName); const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; - this.utils.createSubscription(this, eventName, eventType); - this.utils.setupWatcher(this, eventName, callbacks); + EventUtils.createSubscription(this, eventName, eventType); + EventUtils.setupWatcher(this, eventName, callbacks); } /** @@ -88,7 +87,7 @@ export class EventEmitter { try { const event = this.events.get(eventName); event.eventTime = new Date().toISOString(); - this.utils.writeEvent(event); + EventUtils.writeEvent(event); } catch (err) { throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); } diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts index 55b149d338..5499c8cf41 100644 --- a/packages/imperative/src/events/src/EventEmitterManager.ts +++ b/packages/imperative/src/events/src/EventEmitterManager.ts @@ -10,13 +10,6 @@ */ import { EventEmitter } from "./EventEmitter"; -import { ImperativeError } from "../../error/src/ImperativeError"; -import { dirname, join } from "path"; -import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; -import * as fs from "fs"; -import { ProfileInfo } from "../../config"; -import { IRegisteredAction } from "./doc"; -import { Event } from "./Event"; /** * The EventEmitterManager class serves as a central hub for managing @@ -43,117 +36,4 @@ export class EventEmitterManager { } return this.instances.get(appName); } - - /** - * A collection of helper functions related to event management, including: - * - directory management - * - event type determination - * - subscription creation and setting callbacks - * - event writing - */ - public static Helpers = { - /** - * Determines if the specified event name is a user event. - * - * @param {string} eventName - * @return {boolean} - */ - isUserEvent: function(eventName: string): boolean { - return Object.values(UserEvents).includes(eventName); - }, - - /** - * Determines if the specified event name is a shared event. - * - * @param {string} eventName - * @return {boolean} - */ - isSharedEvent: function(eventName: string): boolean { - return Object.values(SharedEvents).includes(eventName); - }, - - /** - * Retrieves the directory path for events based on the event type and application name. - * - * @param {EventTypes} eventType - * @param {string} appName - * @return {string} - */ - getEventDir: function(eventType: EventTypes, appName: string): string { - return eventType === EventTypes.CustomSharedEvents || eventType === EventTypes.CustomUserEvents ? - join(".events", appName) : ".events"; - }, - - /** - * Ensures that the specified directory for storing event files exists. - * Creates the directory if not. - * - * @param {string} directoryPath - */ - ensureEventsDirExists: function(directoryPath: string) { - try { - if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); - } - } catch (err) { - throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); - } - }, - - /** - * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. - * - * @param {EventEmitter} eeInst The instance of EventEmitter to which the event is registered. - * @param {string} eventName - * @param {EventTypes} eventType - * @return {IRegisteredAction} An object that includes a method to unsubscribe from the event. - */ - createSubscription: function(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction { - const dir = this.getEventDir(eventType, eeInst.appName); - this.ensureEventsDirExists(dir); - const filePath = join(dirname(ProfileInfo.getZoweDir()), eventName); - - const newEvent = new Event({ - eventTime: new Date().toISOString(), - eventName: eventName, - eventType: eventType, - appName: eeInst.appName, - filePath: filePath, - subscriptions: [] - }); - - eeInst.events.set(eventName, newEvent); - - return { - close: () => eeInst.unsubscribe(eventName) - }; - }, - - setupWatcher: function(eeInst: EventEmitter, eventName: string, callbacks: Function[] = []): fs.FSWatcher { - const event = eeInst.events.get(eventName); - const watcher = fs.watch(event.filePath, (trigger: "rename" | "change") => { - // Accommodates for the delay between actual file change event and fsWatcher's perception - //(Node.JS triggers this notification event 3 times) - if (eeInst.eventTimes.get(eventName) !== event.eventTime) { - eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); - // Promise.all(callbacks) - callbacks.forEach(cb => cb()); - eeInst.eventTimes.set(eventName, event.eventTime); - } - }); - event.subscriptions.push(watcher); - return watcher; - }, - - /** - * Writes the specified event to its corresponding file. - * - * @param {Event} event - */ - writeEvent: function(event: Event) { - const eventPath = join(ProfileInfo.getZoweDir(), event.filePath); - this.ensureEventsDirExists(eventPath); - fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); - } - }; } \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts new file mode 100644 index 0000000000..958589041d --- /dev/null +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -0,0 +1,133 @@ +/* +* 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 { ImperativeError } from "../../error/src/ImperativeError"; +import { dirname, join } from "path"; +import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; +import * as fs from "fs"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { IRegisteredAction } from "./doc"; +import { Event } from "./Event"; +import { EventEmitter } from "./EventEmitter"; + +/** + * A collection of helper functions related to event management, including: + * - directory management + * - event type determination + * - subscription creation and setting callbacks + * - event writing + */ +export class EventUtils { + + /** + * Determines if the specified event name is a user event. + * + * @param {string} eventName + * @return {boolean} + */ + public static isUserEvent(eventName: string): boolean { + return Object.values(UserEvents).includes(eventName); + } + + /** + * Determines if the specified event name is a shared event. + * + * @param {string} eventName + * @return {boolean} + */ + public static isSharedEvent(eventName: string): boolean { + return Object.values(SharedEvents).includes(eventName); + } + + /** + * Retrieves the directory path for events based on the event type and application name. + * + * @param {EventTypes} eventType + * @param {string} appName + * @return {string} + */ + public static getEventDir(eventType: EventTypes, appName: string): string { + return eventType === EventTypes.CustomSharedEvents || eventType === EventTypes.CustomUserEvents ? + join(".events", appName) : ".events"; + } + + /** + * Ensures that the specified directory for storing event files exists. + * Creates the directory if not. + * + * @param {string} directoryPath + */ + public static ensureEventsDirExists(directoryPath: string) { + try { + if (!fs.existsSync(directoryPath)) { + fs.mkdirSync(directoryPath); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); + } + } + + /** + * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. + * + * @param {EventEmitter} eeInst The instance of EventEmitter to which the event is registered. + * @param {string} eventName + * @param {EventTypes} eventType + * @return {IRegisteredAction} An object that includes a method to unsubscribe from the event. + */ + public static createSubscription(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction { + const dir = this.getEventDir(eventType, eeInst.appName); + this.ensureEventsDirExists(dir); + const filePath = join(dirname(ConfigUtils.getZoweDir()), eventName); + + const newEvent = new Event({ + eventTime: new Date().toISOString(), + eventName: eventName, + eventType: eventType, + appName: eeInst.appName, + filePath: filePath, + subscriptions: [] + }); + + eeInst.events.set(eventName, newEvent); + + return { + close: () => eeInst.unsubscribe(eventName) + }; + } + + public static setupWatcher(eeInst: EventEmitter, eventName: string, callbacks: Function[] = []): fs.FSWatcher { + const event = eeInst.events.get(eventName); + const watcher = fs.watch(event.filePath, (trigger: "rename" | "change") => { + // Accommodates for the delay between actual file change event and fsWatcher's perception + //(Node.JS triggers this notification event 3 times) + if (eeInst.eventTimes.get(eventName) !== event.eventTime) { + eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); + // Promise.all(callbacks) + callbacks.forEach(cb => cb()); + eeInst.eventTimes.set(eventName, event.eventTime); + } + }); + event.subscriptions.push(watcher); + return watcher; + } + + /** + * Writes the specified event to its corresponding file. + * + * @param {Event} event + */ + public static writeEvent(event: Event) { + const eventPath = join(ConfigUtils.getZoweDir(), event.filePath); + this.ensureEventsDirExists(eventPath); + fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); + } +} \ No newline at end of file diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 1afc159a72..37b5bf6e10 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,7 +16,8 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { EventEmitter, SharedEvents } from "../../events"; +import { SharedEvents } from "../../events"; +import { EventEmitterManager } from "../../events/src/EventEmitterManager"; /** * This class provides access to the known set of credential manager overrides @@ -133,7 +134,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - new EventEmitter("Zowe").emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventEmitterManager.getEmitter('Zowe').emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -188,7 +189,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - new EventEmitter("Zowe").emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventEmitterManager.getEmitter('Zowe').emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From ee563849175a4b923e893bc85e1a3167dd3efeed Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 13 May 2024 16:42:19 -0400 Subject: [PATCH 17/72] pair programming: required fixes found when rebuilding sample app Signed-off-by: Amber Torrise --- .../imperative/src/events/src/EventEmitter.ts | 22 ++++++---- .../src/events/src/EventEmitterManager.ts | 4 +- .../imperative/src/events/src/EventUtils.ts | 42 +++++++++++++------ 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index e73fe4fdce..0d3816342f 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -17,6 +17,7 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { ImperativeConfig } from "../../utilities"; import { EventUtils } from "./EventUtils"; +import { IRegisteredAction } from "./doc"; /** * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. @@ -26,7 +27,7 @@ import { EventUtils } from "./EventUtils"; * @class EventEmitter */ export class EventEmitter { - public events: Map = new Map(); + public subscribedEvents: Map = new Map(); public eventTimes: Map; public appName: string; public logger: Logger; @@ -38,7 +39,7 @@ export class EventEmitter { * @param {Logger} logger The logger instance used for logging information and errors. */ public constructor(appName: string, logger?: Logger) { - this.events = new Map(); + this.subscribedEvents = new Map(); this.appName = appName; // Ensure we have correct environmental conditions to setup a custom logger, @@ -58,10 +59,12 @@ export class EventEmitter { * * @param {string} eventName */ - public subscribeShared(eventName: string): void { + public subscribeShared(eventName: string, callbacks: Function[] | Function): IRegisteredAction { const isCustom = EventUtils.isSharedEvent(eventName); const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; - EventUtils.createSubscription(this, eventName, eventType); + const disposable = EventUtils.createSubscription(this, eventName, eventType); + EventUtils.setupWatcher(this, eventName, callbacks); + return disposable; } /** @@ -69,11 +72,12 @@ export class EventEmitter { * * @param {string} eventName */ - public subscribeUser(eventName: string, callbacks: Function[]): void { + public subscribeUser(eventName: string, callbacks: Function[] | Function): IRegisteredAction { const isCustom = EventUtils.isUserEvent(eventName); const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; - EventUtils.createSubscription(this, eventName, eventType); + const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); + return disposable; } /** @@ -85,7 +89,7 @@ export class EventEmitter { */ public emitEvent(eventName: string): void { try { - const event = this.events.get(eventName); + const event = this.subscribedEvents.get(eventName); event.eventTime = new Date().toISOString(); EventUtils.writeEvent(event); } catch (err) { @@ -103,10 +107,10 @@ export class EventEmitter { public unsubscribe(eventName: string): void { try{ // find watcher list and close everything - this.events.get(eventName).subscriptions.forEach((watcher)=>{ + this.subscribedEvents.get(eventName).subscriptions.forEach((watcher)=>{ watcher.removeAllListeners(eventName).close(); }); - this.events.delete(eventName); + this.subscribedEvents.delete(eventName); } catch(err){ throw new ImperativeError({ msg: `Error unsubscribing from event: ${eventName}`, causeErrors: err }); } diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts index 5499c8cf41..ed0dbc7977 100644 --- a/packages/imperative/src/events/src/EventEmitterManager.ts +++ b/packages/imperative/src/events/src/EventEmitterManager.ts @@ -27,9 +27,9 @@ export class EventEmitterManager { * * @static * @param {string} appName key to KVP for managed event emitter instances - * @return {(EventEmitter | undefined)} Returns the EventEmitter instance or undefined if it cannot be created. + * @return {EventEmitter} Returns the EventEmitter instance */ - public static getEmitter(appName: string): EventEmitter | undefined { + public static getEmitter(appName: string): EventEmitter { if (!this.instances.has(appName)) { const newInstance = new EventEmitter(appName); this.instances.set(appName, newInstance); diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 958589041d..dfb4ddf00a 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -10,7 +10,7 @@ */ import { ImperativeError } from "../../error/src/ImperativeError"; -import { dirname, join } from "path"; +import { join } from "path"; import { UserEvents, SharedEvents, EventTypes } from "./EventConstants"; import * as fs from "fs"; import { ConfigUtils } from "../../config/src/ConfigUtils"; @@ -48,7 +48,7 @@ export class EventUtils { } /** - * Retrieves the directory path for events based on the event type and application name. + * Modifies path to include appName if a custom event type * * @param {EventTypes} eventType * @param {string} appName @@ -75,6 +75,20 @@ export class EventUtils { } } + /** + * Check to see if the file path exists, otherwise, create it : ) + * @param filePath Zowe or User path where we will write the events + */ + public static ensureFileExists(filePath: string) { + try { + if (!fs.existsSync(filePath)) { + fs.closeSync(fs.openSync(filePath, 'w')); + } + } catch (err) { + throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); + } + } + /** * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. * @@ -85,8 +99,11 @@ export class EventUtils { */ public static createSubscription(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction { const dir = this.getEventDir(eventType, eeInst.appName); - this.ensureEventsDirExists(dir); - const filePath = join(dirname(ConfigUtils.getZoweDir()), eventName); + this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), '.events')); + this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), dir)); + + const filePath = join(ConfigUtils.getZoweDir(), dir, eventName); + this.ensureFileExists(filePath); const newEvent = new Event({ eventTime: new Date().toISOString(), @@ -97,22 +114,25 @@ export class EventUtils { subscriptions: [] }); - eeInst.events.set(eventName, newEvent); + eeInst.subscribedEvents.set(eventName, newEvent); return { close: () => eeInst.unsubscribe(eventName) }; } - public static setupWatcher(eeInst: EventEmitter, eventName: string, callbacks: Function[] = []): fs.FSWatcher { - const event = eeInst.events.get(eventName); + public static setupWatcher(eeInst: EventEmitter, eventName: string, callbacks: Function[] | Function ): fs.FSWatcher { + const event = eeInst.subscribedEvents.get(eventName); const watcher = fs.watch(event.filePath, (trigger: "rename" | "change") => { // Accommodates for the delay between actual file change event and fsWatcher's perception //(Node.JS triggers this notification event 3 times) if (eeInst.eventTimes.get(eventName) !== event.eventTime) { eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); - // Promise.all(callbacks) - callbacks.forEach(cb => cb()); + if (Array.isArray(callbacks)){ + callbacks.forEach(cb => cb()); + }else { + callbacks(); + } eeInst.eventTimes.set(eventName, event.eventTime); } }); @@ -126,8 +146,6 @@ export class EventUtils { * @param {Event} event */ public static writeEvent(event: Event) { - const eventPath = join(ConfigUtils.getZoweDir(), event.filePath); - this.ensureEventsDirExists(eventPath); - fs.writeFileSync(eventPath, JSON.stringify(event.toJson(), null, 2)); + fs.writeFileSync(event.filePath, JSON.stringify(event.toJson(), null, 2)); } } \ No newline at end of file From 9305b3e1a34fa4336718fb4ce481efc2d7898952 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 14 May 2024 20:17:38 +0000 Subject: [PATCH 18/72] chore: Address Timothy's feedbach WIP-test: trying to fix integration tests first Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../EventEmitter.integration.test.ts | 101 ++++++++++++++++++ ...ImperativeEventEmitter.integration.test.ts | 38 ------- ...unit.test.ts => EventEmitter.unit.test.ts} | 0 .../src/events/src/EventConstants.ts | 3 +- ...gisteredAction.ts => IDisposableAction.ts} | 6 +- .../imperative/src/events/src/doc/index.ts | 2 +- 6 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts delete mode 100644 packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts rename packages/imperative/src/events/__tests__/__unit__/{ImperativeEventEmitter.unit.test.ts => EventEmitter.unit.test.ts} (100%) rename packages/imperative/src/events/src/doc/{IRegisteredAction.ts => IDisposableAction.ts} (82%) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts new file mode 100644 index 0000000000..7691a3d5fb --- /dev/null +++ b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts @@ -0,0 +1,101 @@ +/* +* 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 { IImperativeEventJson as EventJson, EventUtils, IEventJson, ImperativeEventEmitter, ImperativeSharedEvents } from "../../.."; +import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; +import { TestLogger } from "../../../../__tests__/src/TestLogger"; +import * as TestUtil from "../../../../__tests__/src/TestUtil"; +import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; +import * as fs from "fs"; +import * as path from "path"; + +let TEST_ENVIRONMENT: ITestEnvironment; +const iee = ImperativeEventEmitter; +const iee_s = ImperativeSharedEvents; +let cwd = ''; + +describe("Event Emitter", () => { + const mainModule = process.mainModule; + const testLogger = TestLogger.getTestLogger(); + + beforeAll(async () => { + (process.mainModule as any) = { + filename: __filename + }; + + TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_emitter" + }); + cwd = TEST_ENVIRONMENT.workingDir; + }); + + beforeEach(() => { + iee.initialize("zowe", { logger: testLogger }); + }); + + afterEach(() => { + iee.teardown(); + }); + + afterAll(() => { + process.mainModule = mainModule; + TestUtil.rimraf(cwd); + }); + + const doesEventFileExists = (eventType: string) => { + const eventDir = iee.instance.getEventDir(eventType); + if (!fs.existsSync(eventDir)) return false; + if (fs.existsSync(path.join(eventDir, eventType))) return true; + return false; + }; + + describe("Shared Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { + const theEvent = iee_s.ON_CREDENTIAL_MANAGER_CHANGED; + + expect(doesEventFileExists(theEvent)).toBeFalsy(); + + const subSpy = jest.fn(); + iee.instance.subscribe(theEvent, subSpy); + + expect(subSpy).not.toHaveBeenCalled(); + expect(doesEventFileExists(theEvent)).toBeTruthy(); + + expect(iee.instance.getEventContents(theEvent)).toBeFalsy(); + + iee.instance.emitEvent(theEvent); + + (iee.instance as any).subscriptions.get(theEvent)[1][0](); // simulate FSWatcher called + + expect(doesEventFileExists(theEvent)).toBeTruthy(); + const eventDetails: IEventJson = JSON.parse(iee.instance.getEventContents(theEvent)); + expect(eventDetails.eventName).toEqual(theEvent); + expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeTruthy(); + + expect(subSpy).toHaveBeenCalled(); + }); + it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); + + describe("User Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should trigger subscriptions for all instances watching for onVaultChanged", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); + + describe("Custom Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { }); + it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => { }); + it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); + }); +}); diff --git a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts deleted file mode 100644 index f5154f35e7..0000000000 --- a/packages/imperative/src/events/__tests__/__integration__/ImperativeEventEmitter.integration.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* -* 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 { ImperativeEventEmitter } from "../../src"; - -describe("Event Emitter", () => { - const iee = ImperativeEventEmitter; - - beforeEach(() => { - jest.restoreAllMocks(); - }); - - describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); - it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); - - describe("User Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); - it("should trigger subscriptions for all instances watching for onVaultChanged", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); - - describe("Custom Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); - it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); -}); diff --git a/packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts similarity index 100% rename from packages/imperative/src/events/__tests__/__unit__/ImperativeEventEmitter.unit.test.ts rename to packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts diff --git a/packages/imperative/src/events/src/EventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts index 7bb9b1321e..a2e5727e11 100644 --- a/packages/imperative/src/events/src/EventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -29,7 +29,8 @@ export enum CustomUserEvents { export enum EventTypes { UserEvents, SharedEvents, CustomSharedEvents, CustomUserEvents } -export type EventCallback = () => void | Promise; +export type EventCallback = () => void | PromiseLike; + /** * EXPECTED EVENT LOCATIONS: * diff --git a/packages/imperative/src/events/src/doc/IRegisteredAction.ts b/packages/imperative/src/events/src/doc/IDisposableAction.ts similarity index 82% rename from packages/imperative/src/events/src/doc/IRegisteredAction.ts rename to packages/imperative/src/events/src/doc/IDisposableAction.ts index 0cf5156b1e..2a82f73fdc 100644 --- a/packages/imperative/src/events/src/doc/IRegisteredAction.ts +++ b/packages/imperative/src/events/src/doc/IDisposableAction.ts @@ -12,12 +12,12 @@ /** * Registered Action (possibly change to IDisposableSubscription) * @export - * @interface IRegisteredAction + * @interface IDisposableAction */ -export interface IRegisteredAction { +export interface IDisposableAction { /** * The method to dispose of the registered action - * @memberof IRegisteredAction + * @memberof IDisposableAction */ close(): void; } diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index 35c76f9b31..a38a22e1bf 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,5 +9,5 @@ * */ -export * from "./IRegisteredAction"; +export * from "./IDisposableAction"; export * from "./IEventJson"; From ed5d4684f12c5883b7c766ab20317a551f939701 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Wed, 15 May 2024 13:46:57 +0000 Subject: [PATCH 19/72] chore: fix build issue after renaming IDisposableAction Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 6 +++++- packages/imperative/src/events/src/EventEmitter.ts | 6 +++--- packages/imperative/src/events/src/EventUtils.ts | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index da8c2f648e..646ca839a6 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,9 +2,13 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes + +- Enhancement: Add client-side custom-event handling capabilities. [#2136](https://github.com/zowe/zowe-cli/pull/2136) + ## `8.0.0-next.202405151329` -- Enhancement: Add client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/pull/1987) +- Enhancement: Add client-side event handling capabilities. [#1987](https://github.com/zowe/zowe-cli/issues/1987) ## `8.0.0-next.202405061946` diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventEmitter.ts index 5b004478f8..761f7b48d4 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventEmitter.ts @@ -17,7 +17,7 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { ImperativeConfig } from "../../utilities"; import { EventUtils } from "./EventUtils"; -import { IRegisteredAction } from "./doc"; +import { IDisposableAction } from "./doc"; /** * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. @@ -59,7 +59,7 @@ export class EventEmitter { * * @param {string} eventName */ - public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IRegisteredAction { + public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { const isCustom = EventUtils.isSharedEvent(eventName); const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); @@ -72,7 +72,7 @@ export class EventEmitter { * * @param {string} eventName */ - public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IRegisteredAction { + public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { const isCustom = EventUtils.isUserEvent(eventName); const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 5cd9888177..da25ccda11 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -14,7 +14,7 @@ import { join } from "path"; import { UserEvents, SharedEvents, EventTypes, EventCallback } from "./EventConstants"; import * as fs from "fs"; import { ConfigUtils } from "../../config/src/ConfigUtils"; -import { IRegisteredAction } from "./doc"; +import { IDisposableAction } from "./doc"; import { Event } from "./Event"; import { EventEmitter } from "./EventEmitter"; @@ -95,9 +95,9 @@ export class EventUtils { * @param {EventEmitter} eeInst The instance of EventEmitter to which the event is registered. * @param {string} eventName * @param {EventTypes} eventType - * @return {IRegisteredAction} An object that includes a method to unsubscribe from the event. + * @return {IDisposableAction} An object that includes a method to unsubscribe from the event. */ - public static createSubscription(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IRegisteredAction { + public static createSubscription(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IDisposableAction { const dir = this.getEventDir(eventType, eeInst.appName); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), '.events')); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), dir)); From 8ce30662e85958a14e897c616a8d103cdb5c5b7c Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 15 May 2024 15:18:59 -0400 Subject: [PATCH 20/72] renaming to new conventions, building cleanup methods, building out new interface implementation Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 4 +- .../src/config/src/api/ConfigSecure.ts | 6 +- packages/imperative/src/events/index.ts | 6 +- packages/imperative/src/events/src/Event.ts | 8 +- .../src/events/src/EventConstants.ts | 18 ++- .../src/events/src/EventEmitterManager.ts | 44 ------- .../src/events/src/EventOperator.ts | 119 ++++++++++++++++++ .../{EventEmitter.ts => EventProcessor.ts} | 46 ++++++- .../imperative/src/events/src/EventUtils.ts | 22 ++-- .../src/events/src/doc/IEventInstanceTypes.ts | 20 +++ .../src/events/src/doc/IEventJson.ts | 2 +- .../security/src/CredentialManagerOverride.ts | 8 +- 12 files changed, 221 insertions(+), 82 deletions(-) delete mode 100644 packages/imperative/src/events/src/EventEmitterManager.ts create mode 100644 packages/imperative/src/events/src/EventOperator.ts rename packages/imperative/src/events/src/{EventEmitter.ts => EventProcessor.ts} (66%) create mode 100644 packages/imperative/src/events/src/doc/IEventInstanceTypes.ts diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index 64146412e0..b9344cba89 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -32,7 +32,7 @@ import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; import { Logger } from "../../logger"; -import { EventEmitter } from "../../events/src/EventEmitter"; +import { EventProcessor } from "../../events/src/EventProcessor"; /** * Enum used by Config class to maintain order of config layers @@ -155,7 +155,7 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - new EventEmitter(app, Logger.getAppLogger()); + new EventProcessor(app, Logger.getAppLogger()); // Populate configuration file layers await myNewConfig.reload(opts); diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 0bfc373477..664fadba89 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -20,8 +20,8 @@ import { ConfigConstants } from "../ConfigConstants"; import { IConfigProfile } from "../doc/IConfigProfile"; import { CredentialManagerFactory } from "../../../security"; import { ConfigUtils } from "../ConfigUtils"; -import { UserEvents } from "../../../events/src/EventConstants"; -import { EventEmitterManager } from "../../../events/src/EventEmitterManager"; +import { ZoweUserEvents } from "../../../events/src/EventConstants"; +import { EventOperator } from "../../../events/src/EventOperator"; /** * API Class for manipulating config layers. @@ -132,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - EventEmitterManager.getEmitter('Zowe').emitEvent(UserEvents.ON_VAULT_CHANGED); + EventOperator.getEmitter('Zowe').emitEvent(ZoweUserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index 06e8f18aa5..dc039dca61 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -1,6 +1,6 @@ /* * 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 +* 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 @@ -12,6 +12,6 @@ export * from "./src/doc"; export * from "./src/Event"; export * from "./src/EventConstants"; -export * from "./src/EventEmitter"; -export * from "./src/EventEmitterManager"; +export * from "./src/EventProcessor"; +export * from "./src/EventOperator"; export * from "./src/EventUtils"; \ No newline at end of file diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index 26bd3e91de..13ec01cbb3 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -18,15 +18,15 @@ export class Event implements IEventJson { public eventName: string; public eventType: EventTypes; public appName: string; - public filePath: string; + public eventFilePath: string; public subscriptions: FSWatcher[]; - constructor({ eventTime, eventName, eventType, appName, filePath: eventFilePath, subscriptions }: IEventJson) { + constructor({ eventTime, eventName, eventType, appName, eventFilePath, subscriptions }: IEventJson) { this.eventTime = eventTime; this.eventName = eventName; this.eventType = eventType; this.appName = appName; - this.filePath = eventFilePath; + this.eventFilePath = eventFilePath; this.subscriptions = subscriptions; } @@ -36,7 +36,7 @@ export class Event implements IEventJson { eventName: this.eventName, eventType: this.eventType, appName: this.appName, - eventFilePath: this.filePath + eventFilePath: this.eventFilePath, }; } diff --git a/packages/imperative/src/events/src/EventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts index a2e5727e11..4735f6b1dd 100644 --- a/packages/imperative/src/events/src/EventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -11,23 +11,29 @@ // TO DO - flesh out these enums to include all expected user and shared events - -export enum UserEvents { +/** + * @internal + */ +export enum ZoweUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } -export enum SharedEvents { + +/** + * @internal + */ +export enum ZoweSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } -export enum CustomSharedEvents { +export enum SharedEvents { CUSTOM_SHARED_EVENT = "customSharedEvent" } -export enum CustomUserEvents { +export enum UserEvents { CUSTOM_USER_EVENT = "customUserEvent", } -export enum EventTypes { UserEvents, SharedEvents, CustomSharedEvents, CustomUserEvents } +export enum EventTypes { ZoweUserEvents, ZoweSharedEvents, SharedEvents, UserEvents } export type EventCallback = () => void | PromiseLike; diff --git a/packages/imperative/src/events/src/EventEmitterManager.ts b/packages/imperative/src/events/src/EventEmitterManager.ts deleted file mode 100644 index cde4197817..0000000000 --- a/packages/imperative/src/events/src/EventEmitterManager.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* -* 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 { EventEmitter } from "./EventEmitter"; - -/** - * The EventEmitterManager class serves as a central hub for managing - * event emitters and their watched events. - * - * @export - * @class EventEmitterManager - */ -export class EventEmitterManager { - private static instances: Map = new Map(); - - /** - * Retrieves an existing EventEmitter instance or creates a new one if it does not exist. - * Ensures that each application has a unique EventEmitter instance. - * - * @static - * @param {string} appName key to KVP for managed event emitter instances - * @return {EventEmitter} Returns the EventEmitter instance - */ - public static getEmitter(appName: string): EventEmitter { - if (!this.instances.has(appName)) { - const newInstance = new EventEmitter(appName); - this.instances.set(appName, newInstance); - } - return this.instances.get(appName); - } - - // TODO: Implement `logger` initialization for each emitter initialized - // `EEM.getEmitter(appName, {logger: ...})` - // TODO: Implement `deleteEmitter` that applications can call when shutting down - // `EEM.deleteEmitter(appName)` -} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts new file mode 100644 index 0000000000..b82c8c5f01 --- /dev/null +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -0,0 +1,119 @@ +/* +* 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 { EventProcessor } from "./EventProcessor"; +import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; +import { Logger } from "../../logger"; + +/** + * The EventEmitterManager class serves as a central hub for managing + * event emitters and their watched events. + * + * @export + * @class EventEmitterManager + */ +export class EventOperator { + private static instances: Map = new Map(); + + /** + * Closes and removes processor's file watchers. + * Cleans up environment by deleting the processor instance. + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + private static destroyProcessor(appName: string): void { + if (this.instances.has(appName)) { + const processor = this.instances.get(appName); + processor.subscribedEvents.forEach((event, eventName) => { + event.subscriptions.forEach((subscription) => { + subscription.removeAllListeners(eventName).close(); + }); + }); + this.instances.delete(appName); + } + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IEmitterAndWatcher { + if (!this.instances.has(appName) ) { + const newInstance = new EventProcessor(appName, type, logger); + this.instances.set(appName, newInstance); + } + return this.instances.get(appName); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { + return this.createProcessor(appName, IProcessorTypes.BOTH, logger); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static getWatcher(appName: string, logger?: Logger): IWatcher { + return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static getEmitter(appName: string, logger?: Logger): IEmitter { + return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static deleteProcessor(appName: string) { + this.destroyProcessor(appName); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static deleteWatcher(appName: string) { + this.destroyProcessor(appName); + } + + /** + * + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static deleteEmitter(appName: string) { + this.destroyProcessor(appName); + } +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventEmitter.ts b/packages/imperative/src/events/src/EventProcessor.ts similarity index 66% rename from packages/imperative/src/events/src/EventEmitter.ts rename to packages/imperative/src/events/src/EventProcessor.ts index 761f7b48d4..0c4b0ca20f 100644 --- a/packages/imperative/src/events/src/EventEmitter.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -18,6 +18,7 @@ import { LoggerManager } from "../../logger/src/LoggerManager"; import { ImperativeConfig } from "../../utilities"; import { EventUtils } from "./EventUtils"; import { IDisposableAction } from "./doc"; +import { IProcessorTypes } from "./doc/IEventInstanceTypes"; /** * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. @@ -26,9 +27,10 @@ import { IDisposableAction } from "./doc"; * @export * @class EventEmitter */ -export class EventEmitter { +export class EventProcessor { public subscribedEvents: Map = new Map(); public eventTimes: Map; + public processorType: IProcessorTypes; public appName: string; public logger: Logger; @@ -38,9 +40,10 @@ export class EventEmitter { * @param {string} appName The name of the application this emitter is associated with. * @param {Logger} logger The logger instance used for logging information and errors. */ - public constructor(appName: string, logger?: Logger) { + public constructor(appName: string, type: IProcessorTypes, logger?: Logger) { this.subscribedEvents = new Map(); this.appName = appName; + this.processorType = type; // Ensure we have correct environmental conditions to setup a custom logger, // otherwise use default logger @@ -60,8 +63,11 @@ export class EventEmitter { * @param {string} eventName */ public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + } const isCustom = EventUtils.isSharedEvent(eventName); - const eventType = isCustom ? EventTypes.CustomSharedEvents : EventTypes.SharedEvents; + const eventType = isCustom ? EventTypes.SharedEvents : EventTypes.ZoweSharedEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); return disposable; @@ -73,8 +79,11 @@ export class EventEmitter { * @param {string} eventName */ public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + } const isCustom = EventUtils.isUserEvent(eventName); - const eventType = isCustom ? EventTypes.CustomUserEvents : EventTypes.UserEvents; + const eventType = isCustom ? EventTypes.UserEvents : EventTypes.ZoweUserEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); return disposable; @@ -88,6 +97,32 @@ export class EventEmitter { * @throws {ImperativeError} */ public emitEvent(eventName: string): void { + if (this.processorType === IProcessorTypes.WATCHER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + } + if (EventUtils.isUserEvent(eventName) || EventUtils.isSharedEvent(eventName)) { + throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}`}); + } + try { + const event = this.subscribedEvents.get(eventName); + event.eventTime = new Date().toISOString(); + EventUtils.writeEvent(event); + } catch (err) { + throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); + } + } + + /** + * Emits an event by updating the event time and writing the event data to the associated event file. + * This method throws an error if the event cannot be written. + * @internal Internal Zowe emitter method + * @param {string} eventName + * @throws {ImperativeError} + */ + public emitZoweEvent(eventName: string): void { + if (this.processorType === IProcessorTypes.WATCHER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + } try { const event = this.subscribedEvents.get(eventName); event.eventTime = new Date().toISOString(); @@ -105,6 +140,9 @@ export class EventEmitter { * @param {string} eventName */ public unsubscribe(eventName: string): void { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + } try{ // find watcher list and close everything this.subscribedEvents.get(eventName).subscriptions.forEach((watcher)=>{ diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index da25ccda11..ee104f0fec 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -11,12 +11,12 @@ import { ImperativeError } from "../../error/src/ImperativeError"; import { join } from "path"; -import { UserEvents, SharedEvents, EventTypes, EventCallback } from "./EventConstants"; +import { ZoweUserEvents, ZoweSharedEvents, EventTypes, EventCallback } from "./EventConstants"; import * as fs from "fs"; import { ConfigUtils } from "../../config/src/ConfigUtils"; import { IDisposableAction } from "./doc"; import { Event } from "./Event"; -import { EventEmitter } from "./EventEmitter"; +import { EventProcessor } from "./EventProcessor"; /** * A collection of helper functions related to event management, including: @@ -34,7 +34,7 @@ export class EventUtils { * @return {boolean} */ public static isUserEvent(eventName: string): boolean { - return Object.values(UserEvents).includes(eventName); + return Object.values(ZoweUserEvents).includes(eventName); } /** @@ -44,7 +44,7 @@ export class EventUtils { * @return {boolean} */ public static isSharedEvent(eventName: string): boolean { - return Object.values(SharedEvents).includes(eventName); + return Object.values(ZoweSharedEvents).includes(eventName); } /** @@ -55,7 +55,7 @@ export class EventUtils { * @return {string} */ public static getEventDir(eventType: EventTypes, appName: string): string { - return eventType === EventTypes.CustomSharedEvents || eventType === EventTypes.CustomUserEvents ? + return eventType === EventTypes.SharedEvents || eventType === EventTypes.UserEvents ? join(".events", appName) : ".events"; } @@ -92,12 +92,12 @@ export class EventUtils { /** * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. * - * @param {EventEmitter} eeInst The instance of EventEmitter to which the event is registered. + * @param {EventProcessor} eeInst The instance of EventEmitter to which the event is registered. * @param {string} eventName * @param {EventTypes} eventType * @return {IDisposableAction} An object that includes a method to unsubscribe from the event. */ - public static createSubscription(eeInst: EventEmitter, eventName: string, eventType: EventTypes): IDisposableAction { + public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IDisposableAction { const dir = this.getEventDir(eventType, eeInst.appName); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), '.events')); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), dir)); @@ -110,7 +110,7 @@ export class EventUtils { eventName: eventName, eventType: eventType, appName: eeInst.appName, - filePath: filePath, + eventFilePath: filePath, subscriptions: [] }); @@ -121,9 +121,9 @@ export class EventUtils { }; } - public static setupWatcher(eeInst: EventEmitter, eventName: string, callbacks: EventCallback[] | EventCallback): fs.FSWatcher { + public static setupWatcher(eeInst: EventProcessor, eventName: string, callbacks: EventCallback[] | EventCallback): fs.FSWatcher { const event = eeInst.subscribedEvents.get(eventName); - const watcher = fs.watch(event.filePath, (trigger: "rename" | "change") => { + const watcher = fs.watch(event.eventFilePath, (trigger: "rename" | "change") => { // Accommodates for the delay between actual file change event and fsWatcher's perception //(Node.JS triggers this notification event 3 times) if (eeInst.eventTimes.get(eventName) !== event.eventTime) { @@ -146,6 +146,6 @@ export class EventUtils { * @param {Event} event */ public static writeEvent(event: Event) { - fs.writeFileSync(event.filePath, JSON.stringify(event.toJson(), null, 2)); + fs.writeFileSync(event.eventFilePath, JSON.stringify(event.toJson(), null, 2)); } } \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts new file mode 100644 index 0000000000..019a85f444 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts @@ -0,0 +1,20 @@ +import { EventCallback } from "../EventConstants"; +import { IDisposableAction } from "./IDisposableAction"; + +export interface IWatcher { + subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction; + subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction; + unsubscribe(eventName: string): void; +} + +export interface IEmitter { + emitEvent(eventName: string): void; +} + +export interface IEmitterAndWatcher extends IWatcher, IEmitter {} + +export enum IProcessorTypes { + WATCHER = 'watcher', + EMITTER = 'emitter', + BOTH = 'both' +} diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 81b851a3b4..5189be5733 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -37,7 +37,7 @@ export interface IEventJson { /** * The file path for information on the emitted event */ - filePath: string; + eventFilePath: string; /** * List of watchers to eventually close */ diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 37b5bf6e10..4e6be54e4b 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -16,8 +16,8 @@ import { ICredentialManagerNameMap } from "./doc/ICredentialManagerNameMap"; import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { SharedEvents } from "../../events"; -import { EventEmitterManager } from "../../events/src/EventEmitterManager"; +import { ZoweSharedEvents } from "../../events"; +import { EventOperator } from "../../events/src/EventOperator"; /** * This class provides access to the known set of credential manager overrides @@ -134,7 +134,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventEmitterManager.getEmitter('Zowe').emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getEmitter('Zowe').emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -189,7 +189,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventEmitterManager.getEmitter('Zowe').emitEvent(SharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getEmitter('Zowe').emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From 37a8171768bd92dead8904e1ea016ce2679aa61d Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 15 May 2024 15:40:38 -0400 Subject: [PATCH 21/72] fix build issues after changing naming conventions Signed-off-by: Amber Torrise --- packages/imperative/src/config/src/Config.ts | 6 +----- .../imperative/src/config/src/ProfileInfo.ts | 1 - .../src/config/src/api/ConfigSecure.ts | 2 +- packages/imperative/src/events/index.ts | 2 +- .../src/events/src/EventOperator.ts | 20 ++++++++++++++++++- .../src/events/src/doc/IEventInstanceTypes.ts | 14 ++++++++++++- .../security/src/CredentialManagerOverride.ts | 4 ++-- 7 files changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/imperative/src/config/src/Config.ts b/packages/imperative/src/config/src/Config.ts index b9344cba89..3c7a4f6edd 100644 --- a/packages/imperative/src/config/src/Config.ts +++ b/packages/imperative/src/config/src/Config.ts @@ -31,8 +31,6 @@ import { ConfigUtils } from "./ConfigUtils"; import { IConfigSchemaInfo } from "./doc/IConfigSchema"; import { JsUtils } from "../../utilities/src/JsUtils"; import { IConfigMergeOpts } from "./doc/IConfigMergeOpts"; -import { Logger } from "../../logger"; -import { EventProcessor } from "../../events/src/EventProcessor"; /** * Enum used by Config class to maintain order of config layers @@ -155,8 +153,6 @@ export class Config { myNewConfig.mVault = opts.vault; myNewConfig.mSecure = {}; - new EventProcessor(app, Logger.getAppLogger()); - // Populate configuration file layers await myNewConfig.reload(opts); @@ -247,7 +243,7 @@ export class Config { * Get absolute file path for a config layer. * For project config files, We search up from our current directory and * ignore the Zowe hone directory (in case our current directory is under - * Zowe home.). For golbal config files we only retrieve config files + * Zowe home.). For global config files we only retrieve config files * from the Zowe home directory. * * @internal diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 06c2e795db..de5c80714e 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -10,7 +10,6 @@ */ import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import * as url from "url"; import * as jsonfile from "jsonfile"; diff --git a/packages/imperative/src/config/src/api/ConfigSecure.ts b/packages/imperative/src/config/src/api/ConfigSecure.ts index 664fadba89..bfa8e7a477 100644 --- a/packages/imperative/src/config/src/api/ConfigSecure.ts +++ b/packages/imperative/src/config/src/api/ConfigSecure.ts @@ -132,7 +132,7 @@ export class ConfigSecure extends ConfigApi { */ public async directSave() { await this.mConfig.mVault.save(ConfigConstants.SECURE_ACCT, JSONC.stringify(this.mConfig.mSecure)); - EventOperator.getEmitter('Zowe').emitEvent(ZoweUserEvents.ON_VAULT_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweUserEvents.ON_VAULT_CHANGED); } // _______________________________________________________________________ diff --git a/packages/imperative/src/events/index.ts b/packages/imperative/src/events/index.ts index dc039dca61..0c3a6c5326 100644 --- a/packages/imperative/src/events/index.ts +++ b/packages/imperative/src/events/index.ts @@ -1,6 +1,6 @@ /* * 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 +* 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 diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index b82c8c5f01..0946cb43e1 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -13,6 +13,13 @@ import { EventProcessor } from "./EventProcessor"; import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; import { Logger } from "../../logger"; +/** + * @internal Interface to allow for internal Zowe event emission + */ +interface IZoweProcessor extends IEmitterAndWatcher { + emitZoweEvent(eventName: string): void; +} + /** * The EventEmitterManager class serves as a central hub for managing * event emitters and their watched events. @@ -49,7 +56,7 @@ export class EventOperator { * @param {string} appName key to KVP for managed event processor instances * @return {EventProcessor} Returns the EventProcessor instance */ - private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IEmitterAndWatcher { + private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { if (!this.instances.has(appName) ) { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); @@ -57,6 +64,17 @@ export class EventOperator { return this.instances.get(appName); } + /** + * + * @internal + * @static + * @param {string} appName key to KVP for managed event processor instances + * @return {EventProcessor} Returns the EventProcessor instance + */ + public static getZoweProcessor(): IZoweProcessor { + return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); + } + /** * * @static diff --git a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts index 019a85f444..e9d5816f08 100644 --- a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts +++ b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts @@ -1,3 +1,14 @@ +/* +* 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 { EventCallback } from "../EventConstants"; import { IDisposableAction } from "./IDisposableAction"; @@ -16,5 +27,6 @@ export interface IEmitterAndWatcher extends IWatcher, IEmitter {} export enum IProcessorTypes { WATCHER = 'watcher', EMITTER = 'emitter', - BOTH = 'both' + BOTH = 'both', + //ZOWE = 'zowe' } diff --git a/packages/imperative/src/security/src/CredentialManagerOverride.ts b/packages/imperative/src/security/src/CredentialManagerOverride.ts index 4e6be54e4b..da6eecedb1 100644 --- a/packages/imperative/src/security/src/CredentialManagerOverride.ts +++ b/packages/imperative/src/security/src/CredentialManagerOverride.ts @@ -134,7 +134,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = newCredMgrName; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventOperator.getEmitter('Zowe').emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + @@ -189,7 +189,7 @@ export class CredentialManagerOverride { settings.json.overrides.CredentialManager = this.DEFAULT_CRED_MGR_NAME; try { writeJsonSync(settings.fileName, settings.json, {spaces: 2}); - EventOperator.getEmitter('Zowe').emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); + EventOperator.getZoweProcessor().emitZoweEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); } catch (error) { throw new ImperativeError({ msg: "Unable to write settings file = " + settings.fileName + From e0cc07d7288d9872eea8a8a09b0fb16567f0423b Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 15 May 2024 16:36:44 -0400 Subject: [PATCH 22/72] chore: prevent unkown application names from being from getting a processor Signed-off-by: Amber Torrise Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ConfigUtils.ts | 36 ++++++++++++++++++ .../imperative/src/config/src/ProfileInfo.ts | 22 ++--------- .../src/events/src/EventConstants.ts | 6 --- .../src/events/src/EventOperator.ts | 37 ++++++++++++++++++- 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index d2536671af..5e061dd265 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -12,6 +12,7 @@ import { homedir as osHomedir } from "os"; import { normalize as pathNormalize, join as pathJoin } from "path"; import { existsSync as fsExistsSync } from "fs"; +import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory"; import { ICommandArguments } from "../../cmd"; @@ -21,6 +22,7 @@ import { LoggerManager } from "../../logger/src/LoggerManager"; import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { Logger } from "../../logger/src/Logger"; import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; +import { IExtendersJsonOpts } from "./doc/IExtenderOpts"; export class ConfigUtils { /** @@ -39,6 +41,40 @@ export class ConfigUtils { } return ImperativeConfig.instance.cliHome; } + + /** + * Reads the `extenders.json` file from the CLI home directory. + * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. + * @internal + */ + public static readExtendersJsonFromDisk(): IExtendersJsonOpts { + const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + if (!fsExistsSync(extenderJsonPath)) { + jsonfile.writeFileSync(extenderJsonPath, { + profileTypes: {} + }, { spaces: 4 }); + return { profileTypes: {} }; + } else { + return jsonfile.readFileSync(extenderJsonPath); + } + } + + /** + * Attempts to write to the `extenders.json` file in the CLI home directory. + * @returns `true` if written successfully; `false` otherwise + * @internal + */ + public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { + try { + const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); + } catch (err) { + return false; + } + + return true; + } + /** * Coerces string property value to a boolean or number type. * @param value String value diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index de5c80714e..bb19db3d06 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -10,7 +10,6 @@ */ import * as fs from "fs"; -import * as path from "path"; import * as url from "url"; import * as jsonfile from "jsonfile"; import * as lodash from "lodash"; @@ -1008,33 +1007,20 @@ export class ProfileInfo { * Reads the `extenders.json` file from the CLI home directory. * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal + * @deprecated Please use `ConfigUtils.readExtendersJsonFromDisk` instead */ public static readExtendersJsonFromDisk(): IExtendersJsonOpts { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - if (!fs.existsSync(extenderJsonPath)) { - jsonfile.writeFileSync(extenderJsonPath, { - profileTypes: {} - }, { spaces: 4 }); - return { profileTypes: {} }; - } else { - return jsonfile.readFileSync(extenderJsonPath); - } + return ConfigUtils.readExtendersJsonFromDisk(); } /** * Attempts to write to the `extenders.json` file in the CLI home directory. * @returns `true` if written successfully; `false` otherwise * @internal + * @deprecated Please use `ConfigUtils.writeExtendersJson` instead */ public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { - try { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); - } catch (err) { - return false; - } - - return true; + return ConfigUtils.writeExtendersJson(obj); } /** diff --git a/packages/imperative/src/events/src/EventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts index 4735f6b1dd..075527b89b 100644 --- a/packages/imperative/src/events/src/EventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -11,16 +11,10 @@ // TO DO - flesh out these enums to include all expected user and shared events -/** - * @internal - */ export enum ZoweUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } -/** - * @internal - */ export enum ZoweSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 0946cb43e1..bb811f58c8 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -12,6 +12,8 @@ import { EventProcessor } from "./EventProcessor"; import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; import { Logger } from "../../logger"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { ImperativeError } from "../../error/src/ImperativeError"; /** * @internal Interface to allow for internal Zowe event emission @@ -57,6 +59,15 @@ export class EventOperator { * @return {EventProcessor} Returns the EventProcessor instance */ private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { + const appList = this.getListOfApps(); + // Allow for the Zowe processor and processors in the list of apps installed on the system + if (appName !== "Zowe" && !appList.includes(appName)) { + throw new ImperativeError({ + msg: `Application name not found: ${appName}` + + `Please use an application name from the list:\n- ${appList.join("\n- ")}` + }); + } + if (!this.instances.has(appName) ) { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); @@ -75,6 +86,30 @@ export class EventOperator { return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); } + + /** + * + * @static + * @return {string[]} + */ + public static getListOfApps(): string[] { + const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); + return Object.keys(extendersJson.profileTypes); + /** + * TODO: Do we want them to use the any of the following identifiers as the appName? + * - plug-in name, + * - VSCode extension ID, + * - a user-friendly name (e.g. Zowe Explorer) + * - nothing but the profile types (e.g. sample, zftp, cics, ...) + */ + // const apps: string[] = []; + // for (const [profileType, source] of Object.entries(extendersJson.profileTypes)) { + // apps.push(profileType); + // apps.push(...source.from); + // } + // return apps; + } + /** * * @static @@ -91,7 +126,7 @@ export class EventOperator { * @param {string} appName key to KVP for managed event processor instances * @return {EventProcessor} Returns the EventProcessor instance */ - public static getWatcher(appName: string, logger?: Logger): IWatcher { + public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); } From 4a9f946a7991db3bcc07a67ece942dfe0979f050 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Wed, 15 May 2024 16:36:44 -0400 Subject: [PATCH 23/72] chore: prevent unkown application names from being from getting a processor Signed-off-by: Amber Torrise Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ConfigUtils.ts | 36 ++++++++++++++++++ .../imperative/src/config/src/ProfileInfo.ts | 22 ++--------- .../src/events/src/EventConstants.ts | 6 --- .../src/events/src/EventOperator.ts | 37 ++++++++++++++++++- 4 files changed, 76 insertions(+), 25 deletions(-) diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index d2536671af..5e061dd265 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -12,6 +12,7 @@ import { homedir as osHomedir } from "os"; import { normalize as pathNormalize, join as pathJoin } from "path"; import { existsSync as fsExistsSync } from "fs"; +import * as jsonfile from "jsonfile"; import { CredentialManagerFactory } from "../../security/src/CredentialManagerFactory"; import { ICommandArguments } from "../../cmd"; @@ -21,6 +22,7 @@ import { LoggerManager } from "../../logger/src/LoggerManager"; import { LoggingConfigurer } from "../../imperative/src/LoggingConfigurer"; import { Logger } from "../../logger/src/Logger"; import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; +import { IExtendersJsonOpts } from "./doc/IExtenderOpts"; export class ConfigUtils { /** @@ -39,6 +41,40 @@ export class ConfigUtils { } return ImperativeConfig.instance.cliHome; } + + /** + * Reads the `extenders.json` file from the CLI home directory. + * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. + * @internal + */ + public static readExtendersJsonFromDisk(): IExtendersJsonOpts { + const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + if (!fsExistsSync(extenderJsonPath)) { + jsonfile.writeFileSync(extenderJsonPath, { + profileTypes: {} + }, { spaces: 4 }); + return { profileTypes: {} }; + } else { + return jsonfile.readFileSync(extenderJsonPath); + } + } + + /** + * Attempts to write to the `extenders.json` file in the CLI home directory. + * @returns `true` if written successfully; `false` otherwise + * @internal + */ + public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { + try { + const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); + } catch (err) { + return false; + } + + return true; + } + /** * Coerces string property value to a boolean or number type. * @param value String value diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index de5c80714e..bb19db3d06 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -10,7 +10,6 @@ */ import * as fs from "fs"; -import * as path from "path"; import * as url from "url"; import * as jsonfile from "jsonfile"; import * as lodash from "lodash"; @@ -1008,33 +1007,20 @@ export class ProfileInfo { * Reads the `extenders.json` file from the CLI home directory. * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal + * @deprecated Please use `ConfigUtils.readExtendersJsonFromDisk` instead */ public static readExtendersJsonFromDisk(): IExtendersJsonOpts { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - if (!fs.existsSync(extenderJsonPath)) { - jsonfile.writeFileSync(extenderJsonPath, { - profileTypes: {} - }, { spaces: 4 }); - return { profileTypes: {} }; - } else { - return jsonfile.readFileSync(extenderJsonPath); - } + return ConfigUtils.readExtendersJsonFromDisk(); } /** * Attempts to write to the `extenders.json` file in the CLI home directory. * @returns `true` if written successfully; `false` otherwise * @internal + * @deprecated Please use `ConfigUtils.writeExtendersJson` instead */ public static writeExtendersJson(obj: IExtendersJsonOpts): boolean { - try { - const extenderJsonPath = path.join(ImperativeConfig.instance.cliHome, "extenders.json"); - jsonfile.writeFileSync(extenderJsonPath, obj, { spaces: 4 }); - } catch (err) { - return false; - } - - return true; + return ConfigUtils.writeExtendersJson(obj); } /** diff --git a/packages/imperative/src/events/src/EventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts index 4735f6b1dd..075527b89b 100644 --- a/packages/imperative/src/events/src/EventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -11,16 +11,10 @@ // TO DO - flesh out these enums to include all expected user and shared events -/** - * @internal - */ export enum ZoweUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } -/** - * @internal - */ export enum ZoweSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 0946cb43e1..bb811f58c8 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -12,6 +12,8 @@ import { EventProcessor } from "./EventProcessor"; import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; import { Logger } from "../../logger"; +import { ConfigUtils } from "../../config/src/ConfigUtils"; +import { ImperativeError } from "../../error/src/ImperativeError"; /** * @internal Interface to allow for internal Zowe event emission @@ -57,6 +59,15 @@ export class EventOperator { * @return {EventProcessor} Returns the EventProcessor instance */ private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { + const appList = this.getListOfApps(); + // Allow for the Zowe processor and processors in the list of apps installed on the system + if (appName !== "Zowe" && !appList.includes(appName)) { + throw new ImperativeError({ + msg: `Application name not found: ${appName}` + + `Please use an application name from the list:\n- ${appList.join("\n- ")}` + }); + } + if (!this.instances.has(appName) ) { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); @@ -75,6 +86,30 @@ export class EventOperator { return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); } + + /** + * + * @static + * @return {string[]} + */ + public static getListOfApps(): string[] { + const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); + return Object.keys(extendersJson.profileTypes); + /** + * TODO: Do we want them to use the any of the following identifiers as the appName? + * - plug-in name, + * - VSCode extension ID, + * - a user-friendly name (e.g. Zowe Explorer) + * - nothing but the profile types (e.g. sample, zftp, cics, ...) + */ + // const apps: string[] = []; + // for (const [profileType, source] of Object.entries(extendersJson.profileTypes)) { + // apps.push(profileType); + // apps.push(...source.from); + // } + // return apps; + } + /** * * @static @@ -91,7 +126,7 @@ export class EventOperator { * @param {string} appName key to KVP for managed event processor instances * @return {EventProcessor} Returns the EventProcessor instance */ - public static getWatcher(appName: string, logger?: Logger): IWatcher { + public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); } From bca063cac51b5f98c14a14da29a5e29f4229d27c Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 21 May 2024 12:19:38 -0400 Subject: [PATCH 24/72] style: modifying comments and interface names for clarity and consistency Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/Event.ts | 20 ++- .../src/events/src/EventOperator.ts | 137 +++++++++--------- .../src/events/src/EventProcessor.ts | 62 ++++---- .../imperative/src/events/src/EventUtils.ts | 62 ++++---- .../src/events/src/doc/IDisposableAction.ts | 23 --- .../src/events/src/doc/IEventDisposable.ts | 24 +++ .../src/events/src/doc/IEventInstanceTypes.ts | 38 +++-- .../imperative/src/events/src/doc/index.ts | 3 +- 8 files changed, 203 insertions(+), 166 deletions(-) delete mode 100644 packages/imperative/src/events/src/doc/IDisposableAction.ts create mode 100644 packages/imperative/src/events/src/doc/IEventDisposable.ts diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index 13ec01cbb3..a4f435840e 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -13,6 +13,10 @@ import { FSWatcher } from "fs"; import { EventTypes } from "./EventConstants"; import { IEventJson } from "./doc"; +/** + * Represents an event within the system, containing all necessary metadata + * and subscriptions related to the event. + */ export class Event implements IEventJson { public eventTime: string; public eventName: string; @@ -21,6 +25,12 @@ export class Event implements IEventJson { public eventFilePath: string; public subscriptions: FSWatcher[]; + + /** + * Initializes a new instance of the Event class with specified properties. + * + * @param {IEventJson} params - The parameters to create the Event. + */ constructor({ eventTime, eventName, eventType, appName, eventFilePath, subscriptions }: IEventJson) { this.eventTime = eventTime; this.eventName = eventName; @@ -30,6 +40,11 @@ export class Event implements IEventJson { this.subscriptions = subscriptions; } + /** + * Serializes the Event object to a JSON object for storage or transmission. + * + * @returns {Object} The JSON representation of the event. + */ public toJson() { return { eventTime: this.eventTime, @@ -41,8 +56,9 @@ export class Event implements IEventJson { } /** - * toString overload to be called automatically on string concatenation - * @returns string representation of the imperative event + * Provides a string representation of the Event, useful for logging and debugging. + * + * @returns {string} A string detailing the event's significant information. */ public toString = (): string => { return `Name: ${this.eventName} \t| Time: ${this.eventTime} \t| App: ${this.appName} \t| Type: ${this.eventType}`; diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index bb811f58c8..b09c5b4fa0 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -16,59 +16,66 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { ImperativeError } from "../../error/src/ImperativeError"; /** - * @internal Interface to allow for internal Zowe event emission + * @internal Interface for Zowe-specific event processing, combining emitter and watcher functionalities. */ interface IZoweProcessor extends IEmitterAndWatcher { emitZoweEvent(eventName: string): void; } /** - * The EventEmitterManager class serves as a central hub for managing - * event emitters and their watched events. + * Manages event processors for different applications, facilitating subscription, + * emission, and watching of events. * * @export - * @class EventEmitterManager + * @class EventOperator */ export class EventOperator { private static instances: Map = new Map(); /** - * Closes and removes processor's file watchers. - * Cleans up environment by deleting the processor instance. + * Retrieves a list of supported applications from configuration. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @returns {string[]} List of application names. */ - private static destroyProcessor(appName: string): void { - if (this.instances.has(appName)) { - const processor = this.instances.get(appName); - processor.subscribedEvents.forEach((event, eventName) => { - event.subscriptions.forEach((subscription) => { - subscription.removeAllListeners(eventName).close(); - }); - }); - this.instances.delete(appName); - } + + public static getListOfApps(): string[] { + const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); + return Object.keys(extendersJson.profileTypes); + /** + * TODO: Do we want them to use the any of the following identifiers as the appName? + * - plug-in name, + * - VSCode extension ID, + * - a user-friendly name (e.g. Zowe Explorer) + * - nothing but the profile types (e.g. sample, zftp, cics, ...) + */ + // const apps: string[] = []; + // for (const [profileType, source] of Object.entries(extendersJson.profileTypes)) { + // apps.push(profileType); + // apps.push(...source.from); + // } + // return apps; } /** + * Creates an event processor for a specified application. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The name of the application. + * @param {IProcessorTypes} type - The type of processor to create (emitter, watcher, or both). + * @param {Logger} [logger] - Optional logger instance for the processor. + * @returns {IZoweProcessor} A new or existing processor instance. + * @throws {ImperativeError} If the application name is not recognized. */ private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { const appList = this.getListOfApps(); - // Allow for the Zowe processor and processors in the list of apps installed on the system if (appName !== "Zowe" && !appList.includes(appName)) { throw new ImperativeError({ - msg: `Application name not found: ${appName}` + - `Please use an application name from the list:\n- ${appList.join("\n- ")}` + msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` }); } - if (!this.instances.has(appName) ) { + if (!this.instances.has(appName)) { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); } @@ -76,97 +83,95 @@ export class EventOperator { } /** + * Retrieves a Zowe-specific event processor. * * @internal * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @returns {IZoweProcessor} The Zowe event processor instance. */ public static getZoweProcessor(): IZoweProcessor { return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); } - /** + * Retrieves a generic event processor that can emit and watch events. * * @static - * @return {string[]} + * @param {string} appName - The application name. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IEmitterAndWatcher} An event processor capable of both emitting and watching. */ - public static getListOfApps(): string[] { - const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); - return Object.keys(extendersJson.profileTypes); - /** - * TODO: Do we want them to use the any of the following identifiers as the appName? - * - plug-in name, - * - VSCode extension ID, - * - a user-friendly name (e.g. Zowe Explorer) - * - nothing but the profile types (e.g. sample, zftp, cics, ...) - */ - // const apps: string[] = []; - // for (const [profileType, source] of Object.entries(extendersJson.profileTypes)) { - // apps.push(profileType); - // apps.push(...source.from); - // } - // return apps; + public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { + return this.createProcessor(appName, IProcessorTypes.BOTH, logger); } /** + * Retrieves a watcher-only event processor. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The application name, defaults to "Zowe" if not specified. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IWatcher} A watcher-only event processor. */ - public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { - return this.createProcessor(appName, IProcessorTypes.BOTH, logger); + public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { + return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); } /** + * Retrieves an emitter-only event processor. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The application name. + * @param {Logger} [logger] - Optional logger for the processor. + * @returns {IEmitter} An emitter-only event processor. */ - public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { - return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); + public static getEmitter(appName: string, logger?: Logger): IEmitter { + return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); } /** + * Deletes a specific type of event processor (emitter). * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The application name associated with the emitter to be deleted. */ - public static getEmitter(appName: string, logger?: Logger): IEmitter { - return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); + public static deleteEmitter(appName: string): void { + this.destroyProcessor(appName); } /** + * Deletes a specific type of event processor (watcher). * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The application name associated with the watcher to be deleted. */ - public static deleteProcessor(appName: string) { + public static deleteWatcher(appName: string): void { this.destroyProcessor(appName); } /** + * Deletes an event processor, removing both its emitter and watcher capabilities. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The application name whose processor is to be deleted. */ - public static deleteWatcher(appName: string) { + public static deleteProcessor(appName: string): void { this.destroyProcessor(appName); } /** + * Destroys a processor by removing all associated file watchers and cleaning up resources. * * @static - * @param {string} appName key to KVP for managed event processor instances - * @return {EventProcessor} Returns the EventProcessor instance + * @param {string} appName - The name of the application whose processor needs to be destroyed. */ - public static deleteEmitter(appName: string) { - this.destroyProcessor(appName); + private static destroyProcessor(appName: string): void { + const processor = this.instances.get(appName); + if (processor) { + processor.subscribedEvents.forEach((event, eventName) => { + event.subscriptions.forEach(subscription => subscription.removeAllListeners(eventName).close()); + }); + this.instances.delete(appName); + } } } \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 0c4b0ca20f..103391b865 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -17,15 +17,14 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { LoggerManager } from "../../logger/src/LoggerManager"; import { ImperativeConfig } from "../../utilities"; import { EventUtils } from "./EventUtils"; -import { IDisposableAction } from "./doc"; +import { IEventDisposable } from "./doc"; import { IProcessorTypes } from "./doc/IEventInstanceTypes"; /** - * The EventEmitter class is responsible for managing event subscriptions and emissions for a specific application. - * It maintains a map of subscriptions where each event name is associated with its corresponding Event object. + * Manages event subscriptions and emissions for a specific application. * * @export - * @class EventEmitter + * @class EventProcessor */ export class EventProcessor { public subscribedEvents: Map = new Map(); @@ -34,11 +33,14 @@ export class EventProcessor { public appName: string; public logger: Logger; + + /** - * Creates an instance of EventEmitter. + * Constructor initializes a new instance of EventProcessor. * - * @param {string} appName The name of the application this emitter is associated with. - * @param {Logger} logger The logger instance used for logging information and errors. + * @param {string} appName - The application's name. + * @param {IProcessorTypes} type - The type of processor (Emitter, Watcher, or Both). + * @param {Logger} [logger] - Optional logger for recording events and errors. */ public constructor(appName: string, type: IProcessorTypes, logger?: Logger) { this.subscribedEvents = new Map(); @@ -54,15 +56,13 @@ export class EventProcessor { } /** - * Utility helpers from EventEmitterManager for managing events. - */ - - /** - * Subscribes to a shared event. This method determines the event type and creates a subscription. + * Subscribes to shared events by creating and managing a new subscription. * - * @param {string} eventName + * @param {string} eventName - The name of the event to subscribe to. + * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. + * @returns {IEventDisposable} - Object allowing management of the subscription. */ - public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { + public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); } @@ -74,11 +74,13 @@ export class EventProcessor { } /** - * Subscribes to a user event. This method determines whether the event is custom and creates a subscription accordingly. - * - * @param {string} eventName - */ - public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction { + * Subscribes to user-specific events by creating and managing a new subscription. + * + * @param {string} eventName - The name of the event to subscribe to. + * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. + * @returns {IEventDisposable} - Object allowing management of the subscription. + */ + public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); } @@ -90,11 +92,10 @@ export class EventProcessor { } /** - * Emits an event by updating the event time and writing the event data to the associated event file. - * This method throws an error if the event cannot be written. + * Emits an event by updating its timestamp and writing event data. * - * @param {string} eventName - * @throws {ImperativeError} + * @param {string} eventName - The name of the event to emit. + * @throws {ImperativeError} - If the event cannot be emitted. */ public emitEvent(eventName: string): void { if (this.processorType === IProcessorTypes.WATCHER) { @@ -113,11 +114,11 @@ export class EventProcessor { } /** - * Emits an event by updating the event time and writing the event data to the associated event file. - * This method throws an error if the event cannot be written. + * Specifically emits Zowe-related events, updating timestamps and handling data. + * * @internal Internal Zowe emitter method - * @param {string} eventName - * @throws {ImperativeError} + * @param {string} eventName - The name of the Zowe event to emit. + * @throws {ImperativeError} - If the event cannot be emitted. */ public emitZoweEvent(eventName: string): void { if (this.processorType === IProcessorTypes.WATCHER) { @@ -133,11 +134,10 @@ export class EventProcessor { } /** - * Unsubscribes from a given event by closing the file watchers associated with that event THEN - * deleting that event from the EventEmitter's events map. - * Logs an error if eventName isn't found. + * Unsubscribes from an event, closing file watchers and cleaning up resources. * - * @param {string} eventName + * @param {string} eventName - The name of the event to unsubscribe from. + * @throws {ImperativeError} - If unsubscribing fails. */ public unsubscribe(eventName: string): void { if (this.processorType === IProcessorTypes.EMITTER) { diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index ee104f0fec..a0c550a56c 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -14,12 +14,12 @@ import { join } from "path"; import { ZoweUserEvents, ZoweSharedEvents, EventTypes, EventCallback } from "./EventConstants"; import * as fs from "fs"; import { ConfigUtils } from "../../config/src/ConfigUtils"; -import { IDisposableAction } from "./doc"; +import { IEventDisposable } from "./doc"; import { Event } from "./Event"; import { EventProcessor } from "./EventProcessor"; /** - * A collection of helper functions related to event management, including: + * A collection of helper functions related to event processing, including: * - directory management * - event type determination * - subscription creation and setting callbacks @@ -28,31 +28,31 @@ import { EventProcessor } from "./EventProcessor"; export class EventUtils { /** - * Determines if the specified event name is a user event. - * - * @param {string} eventName - * @return {boolean} - */ + * Determines if the specified event name is associated with a user event. + * + * @param {string} eventName - The name of the event. + * @return {boolean} True if it is a user event, otherwise false. + */ public static isUserEvent(eventName: string): boolean { return Object.values(ZoweUserEvents).includes(eventName); } /** - * Determines if the specified event name is a shared event. + * Determines if the specified event name is associated with a shared event. * - * @param {string} eventName - * @return {boolean} + * @param {string} eventName - The name of the event. + * @return {boolean} True if it is a shared event, otherwise false. */ public static isSharedEvent(eventName: string): boolean { return Object.values(ZoweSharedEvents).includes(eventName); } /** - * Modifies path to include appName if a custom event type + * Determines the directory path for storing event files based on the event type and application name. * - * @param {EventTypes} eventType - * @param {string} appName - * @return {string} + * @param {EventTypes} eventType - The type of event. + * @param {string} appName - The name of the application. + * @return {string} The directory path. */ public static getEventDir(eventType: EventTypes, appName: string): string { return eventType === EventTypes.SharedEvents || eventType === EventTypes.UserEvents ? @@ -60,10 +60,9 @@ export class EventUtils { } /** - * Ensures that the specified directory for storing event files exists. - * Creates the directory if not. + * Ensures that the specified directory for storing event files exists, creating it if necessary. * - * @param {string} directoryPath + * @param {string} directoryPath - The path to the directory. */ public static ensureEventsDirExists(directoryPath: string) { try { @@ -76,8 +75,9 @@ export class EventUtils { } /** - * Check to see if the file path exists, otherwise, create it : ) - * @param filePath Zowe or User path where we will write the events + * Ensures that the specified file path for storing event data exists, creating it if necessary. + * + * @param {string} filePath - The path to the file. */ public static ensureFileExists(filePath: string) { try { @@ -90,14 +90,14 @@ export class EventUtils { } /** - * Creates a subscription for an event. It configures and stores an event instance within the EventEmitter's subscription map. + * Creates and registers a new event subscription for a specific event processor. * - * @param {EventProcessor} eeInst The instance of EventEmitter to which the event is registered. - * @param {string} eventName - * @param {EventTypes} eventType - * @return {IDisposableAction} An object that includes a method to unsubscribe from the event. + * @param {EventProcessor} eeInst - The event processor instance. + * @param {string} eventName - The name of the event. + * @param {EventTypes} eventType - The type of event. + * @return {IEventDisposable} An interface for managing the subscription. */ - public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IDisposableAction { + public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { const dir = this.getEventDir(eventType, eeInst.appName); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), '.events')); this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), dir)); @@ -121,6 +121,14 @@ export class EventUtils { }; } + /** + * Sets up a file watcher for a specific event, triggering callbacks when the event file is updated. + * + * @param {EventProcessor} eeInst - The event processor instance. + * @param {string} eventName - The name of the event. + * @param {EventCallback[] | EventCallback} callbacks - A single callback or an array of callbacks to execute. + * @return {fs.FSWatcher} A file system watcher. + */ public static setupWatcher(eeInst: EventProcessor, eventName: string, callbacks: EventCallback[] | EventCallback): fs.FSWatcher { const event = eeInst.subscribedEvents.get(eventName); const watcher = fs.watch(event.eventFilePath, (trigger: "rename" | "change") => { @@ -141,9 +149,9 @@ export class EventUtils { } /** - * Writes the specified event to its corresponding file. + * Writes event data to the corresponding event file in JSON format. * - * @param {Event} event + * @param {Event} event - The event object. */ public static writeEvent(event: Event) { fs.writeFileSync(event.eventFilePath, JSON.stringify(event.toJson(), null, 2)); diff --git a/packages/imperative/src/events/src/doc/IDisposableAction.ts b/packages/imperative/src/events/src/doc/IDisposableAction.ts deleted file mode 100644 index 2a82f73fdc..0000000000 --- a/packages/imperative/src/events/src/doc/IDisposableAction.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* -* 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. -* -*/ - -/** - * Registered Action (possibly change to IDisposableSubscription) - * @export - * @interface IDisposableAction - */ -export interface IDisposableAction { - /** - * The method to dispose of the registered action - * @memberof IDisposableAction - */ - close(): void; -} diff --git a/packages/imperative/src/events/src/doc/IEventDisposable.ts b/packages/imperative/src/events/src/doc/IEventDisposable.ts new file mode 100644 index 0000000000..5dc7e58099 --- /dev/null +++ b/packages/imperative/src/events/src/doc/IEventDisposable.ts @@ -0,0 +1,24 @@ +/* +* 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. +* +*/ + +/** + * Defines a contract for objects that can dispose of resources typically associated with an event. + * (Any object that implements this interface must provide an implementation of the close() method) + * @export + * @interface IEventDisposable + */ +export interface IEventDisposable { + /** + * Disposes of the actions registered to an event, effectively cleaning up or removing event handlers. + * @memberof IEventDisposable + */ + close(): void; +} diff --git a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts index e9d5816f08..c301afe86e 100644 --- a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts +++ b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts @@ -1,32 +1,38 @@ -/* -* 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 { EventCallback } from "../EventConstants"; -import { IDisposableAction } from "./IDisposableAction"; +import { IEventDisposable } from "./IEventDisposable"; +/** + * Interface for components that can subscribe to and unsubscribe from events. + * @interface IWatcher + */ export interface IWatcher { - subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction; - subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IDisposableAction; + subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable; + subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable; unsubscribe(eventName: string): void; } +/** + * Interface for components that can emit events. + * @interface IEmitter + */ export interface IEmitter { emitEvent(eventName: string): void; } +/** + * Interface for components that can both emit and watch events. + * Combines the capabilities of both IWatcher and IEmitter interfaces. + * @interface IEmitterAndWatcher + */ export interface IEmitterAndWatcher extends IWatcher, IEmitter {} +/** + * Enum representing the types of processors that can be used to handle events. + * Specifies whether the processor is a watcher, an emitter, or capable of both functions. + * @enum {string} + */ export enum IProcessorTypes { WATCHER = 'watcher', EMITTER = 'emitter', BOTH = 'both', - //ZOWE = 'zowe' -} +} \ No newline at end of file diff --git a/packages/imperative/src/events/src/doc/index.ts b/packages/imperative/src/events/src/doc/index.ts index a38a22e1bf..4a43ef166c 100644 --- a/packages/imperative/src/events/src/doc/index.ts +++ b/packages/imperative/src/events/src/doc/index.ts @@ -9,5 +9,6 @@ * */ -export * from "./IDisposableAction"; +export * from "./IEventDisposable"; +export * from "./IEventInstanceTypes"; export * from "./IEventJson"; From c7e3ea78430b8a9a7f66143419ca0da2caa62469 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 21 May 2024 15:58:34 -0400 Subject: [PATCH 25/72] feat/refactor: deciding what is a valid appName Signed-off-by: Amber Torrise --- .../src/events/src/EventOperator.ts | 40 +++++-------------- .../src/events/src/EventProcessor.ts | 2 - .../imperative/src/events/src/EventUtils.ts | 38 ++++++++++++++++++ .../src/events/src/doc/IEventInstanceTypes.ts | 11 +++++ 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index b09c5b4fa0..2640e3b62b 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -14,6 +14,7 @@ import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/I import { Logger } from "../../logger"; import { ConfigUtils } from "../../config/src/ConfigUtils"; import { ImperativeError } from "../../error/src/ImperativeError"; +import { EventUtils } from "./EventUtils"; /** * @internal Interface for Zowe-specific event processing, combining emitter and watcher functionalities. @@ -32,31 +33,6 @@ interface IZoweProcessor extends IEmitterAndWatcher { export class EventOperator { private static instances: Map = new Map(); - /** - * Retrieves a list of supported applications from configuration. - * - * @static - * @returns {string[]} List of application names. - */ - - public static getListOfApps(): string[] { - const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); - return Object.keys(extendersJson.profileTypes); - /** - * TODO: Do we want them to use the any of the following identifiers as the appName? - * - plug-in name, - * - VSCode extension ID, - * - a user-friendly name (e.g. Zowe Explorer) - * - nothing but the profile types (e.g. sample, zftp, cics, ...) - */ - // const apps: string[] = []; - // for (const [profileType, source] of Object.entries(extendersJson.profileTypes)) { - // apps.push(profileType); - // apps.push(...source.from); - // } - // return apps; - } - /** * Creates an event processor for a specified application. * @@ -68,12 +44,7 @@ export class EventOperator { * @throws {ImperativeError} If the application name is not recognized. */ private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { - const appList = this.getListOfApps(); - if (appName !== "Zowe" && !appList.includes(appName)) { - throw new ImperativeError({ - msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` - }); - } + EventUtils.validateAppName(appName); if (!this.instances.has(appName)) { const newInstance = new EventProcessor(appName, type, logger); @@ -102,6 +73,7 @@ export class EventOperator { * @returns {IEmitterAndWatcher} An event processor capable of both emitting and watching. */ public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { + EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.BOTH, logger); } @@ -114,6 +86,7 @@ export class EventOperator { * @returns {IWatcher} A watcher-only event processor. */ public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { + EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); } @@ -126,6 +99,7 @@ export class EventOperator { * @returns {IEmitter} An emitter-only event processor. */ public static getEmitter(appName: string, logger?: Logger): IEmitter { + EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); } @@ -136,6 +110,7 @@ export class EventOperator { * @param {string} appName - The application name associated with the emitter to be deleted. */ public static deleteEmitter(appName: string): void { + EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -146,6 +121,7 @@ export class EventOperator { * @param {string} appName - The application name associated with the watcher to be deleted. */ public static deleteWatcher(appName: string): void { + EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -156,6 +132,7 @@ export class EventOperator { * @param {string} appName - The application name whose processor is to be deleted. */ public static deleteProcessor(appName: string): void { + EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -166,6 +143,7 @@ export class EventOperator { * @param {string} appName - The name of the application whose processor needs to be destroyed. */ private static destroyProcessor(appName: string): void { + EventUtils.validateAppName(appName); const processor = this.instances.get(appName); if (processor) { processor.subscribedEvents.forEach((event, eventName) => { diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 103391b865..a0b88f3ec4 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -33,8 +33,6 @@ export class EventProcessor { public appName: string; public logger: Logger; - - /** * Constructor initializes a new instance of EventProcessor. * diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index a0c550a56c..8819f3be26 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -27,6 +27,44 @@ import { EventProcessor } from "./EventProcessor"; */ export class EventUtils { + /** + * Retrieves a list of supported applications from configuration. + * + * @static + * @returns {string[]} List of application names. + */ + public static getListOfApps(): string[] { + const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); + const apps: string[] = []; + // Loop through each profile type and accumulate all names and their sources based on conditions. + for (const [profileType, details] of Object.entries(extendersJson.profileTypes)) { + // Check each entry in the 'from' array to decide if a tag is needed + details.from.forEach(item => { + if (item.includes("(for VS Code)")) { + apps.push(profileType, "_vsce"); // tag indicating Visual Studio Code Extension + } else if (item.includes("@zowe")) { + apps.push(profileType); // no tag indicates Zowe CLI plugin (default) + } + }); + } + return apps; + } + + /** + * Won't throw an error if user entered a valid appName + * + * @static + * @param {string} appName - The name of the application. + */ + public static validateAppName(appName: string){ + const appList = this.getListOfApps(); + if (appName !== "Zowe" && !appList.includes(appName)) { + throw new ImperativeError({ + msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` + }); + } + } + /** * Determines if the specified event name is associated with a user event. * diff --git a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts index c301afe86e..83615bfef0 100644 --- a/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts +++ b/packages/imperative/src/events/src/doc/IEventInstanceTypes.ts @@ -1,3 +1,14 @@ +/* +* 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 { EventCallback } from "../EventConstants"; import { IEventDisposable } from "./IEventDisposable"; From 1eaad246ecff25c7596206930d3df232bd85e58d Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 21 May 2024 16:17:29 -0400 Subject: [PATCH 26/72] refactor: unsure if this line is necessary apps.push(Zowe) Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/EventOperator.ts | 3 --- packages/imperative/src/events/src/EventProcessor.ts | 2 ++ packages/imperative/src/events/src/EventUtils.ts | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 2640e3b62b..3c8b1a865a 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -12,8 +12,6 @@ import { EventProcessor } from "./EventProcessor"; import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; import { Logger } from "../../logger"; -import { ConfigUtils } from "../../config/src/ConfigUtils"; -import { ImperativeError } from "../../error/src/ImperativeError"; import { EventUtils } from "./EventUtils"; /** @@ -86,7 +84,6 @@ export class EventOperator { * @returns {IWatcher} A watcher-only event processor. */ public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { - EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); } diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index a0b88f3ec4..f017ce4032 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -41,6 +41,8 @@ export class EventProcessor { * @param {Logger} [logger] - Optional logger for recording events and errors. */ public constructor(appName: string, type: IProcessorTypes, logger?: Logger) { + EventUtils.validateAppName(appName); + this.subscribedEvents = new Map(); this.appName = appName; this.processorType = type; diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 8819f3be26..429ece0462 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -46,6 +46,7 @@ export class EventUtils { apps.push(profileType); // no tag indicates Zowe CLI plugin (default) } }); + apps.push("Zowe"); // default application name } return apps; } @@ -93,6 +94,7 @@ export class EventUtils { * @return {string} The directory path. */ public static getEventDir(eventType: EventTypes, appName: string): string { + this.validateAppName(appName); return eventType === EventTypes.SharedEvents || eventType === EventTypes.UserEvents ? join(".events", appName) : ".events"; } From 5f6ee26bd9be6b7d59d351cfa2e22cfbdaccc8af Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:16:05 +0000 Subject: [PATCH 27/72] fix: custom applications not being allowed Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/events/src/EventOperator.ts | 8 -------- .../src/events/src/EventProcessor.ts | 18 +++++++++--------- .../imperative/src/events/src/EventUtils.ts | 15 ++++++++++++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 3c8b1a865a..3771f336fb 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -42,8 +42,6 @@ export class EventOperator { * @throws {ImperativeError} If the application name is not recognized. */ private static createProcessor(appName: string, type: IProcessorTypes, logger?: Logger): IZoweProcessor { - EventUtils.validateAppName(appName); - if (!this.instances.has(appName)) { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); @@ -71,7 +69,6 @@ export class EventOperator { * @returns {IEmitterAndWatcher} An event processor capable of both emitting and watching. */ public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { - EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.BOTH, logger); } @@ -96,7 +93,6 @@ export class EventOperator { * @returns {IEmitter} An emitter-only event processor. */ public static getEmitter(appName: string, logger?: Logger): IEmitter { - EventUtils.validateAppName(appName); return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); } @@ -107,7 +103,6 @@ export class EventOperator { * @param {string} appName - The application name associated with the emitter to be deleted. */ public static deleteEmitter(appName: string): void { - EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -118,7 +113,6 @@ export class EventOperator { * @param {string} appName - The application name associated with the watcher to be deleted. */ public static deleteWatcher(appName: string): void { - EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -129,7 +123,6 @@ export class EventOperator { * @param {string} appName - The application name whose processor is to be deleted. */ public static deleteProcessor(appName: string): void { - EventUtils.validateAppName(appName); this.destroyProcessor(appName); } @@ -140,7 +133,6 @@ export class EventOperator { * @param {string} appName - The name of the application whose processor needs to be destroyed. */ private static destroyProcessor(appName: string): void { - EventUtils.validateAppName(appName); const processor = this.instances.get(appName); if (processor) { processor.subscribedEvents.forEach((event, eventName) => { diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index f017ce4032..0ee7e8a01b 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -64,7 +64,7 @@ export class EventProcessor { */ public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } const isCustom = EventUtils.isSharedEvent(eventName); const eventType = isCustom ? EventTypes.SharedEvents : EventTypes.ZoweSharedEvents; @@ -82,7 +82,7 @@ export class EventProcessor { */ public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } const isCustom = EventUtils.isUserEvent(eventName); const eventType = isCustom ? EventTypes.UserEvents : EventTypes.ZoweUserEvents; @@ -99,10 +99,10 @@ export class EventProcessor { */ public emitEvent(eventName: string): void { if (this.processorType === IProcessorTypes.WATCHER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } if (EventUtils.isUserEvent(eventName) || EventUtils.isSharedEvent(eventName)) { - throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}`}); + throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}` }); } try { const event = this.subscribedEvents.get(eventName); @@ -122,7 +122,7 @@ export class EventProcessor { */ public emitZoweEvent(eventName: string): void { if (this.processorType === IProcessorTypes.WATCHER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } try { const event = this.subscribedEvents.get(eventName); @@ -141,15 +141,15 @@ export class EventProcessor { */ public unsubscribe(eventName: string): void { if (this.processorType === IProcessorTypes.EMITTER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}`}); + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } - try{ + try { // find watcher list and close everything - this.subscribedEvents.get(eventName).subscriptions.forEach((watcher)=>{ + this.subscribedEvents.get(eventName).subscriptions.forEach((watcher) => { watcher.removeAllListeners(eventName).close(); }); this.subscribedEvents.delete(eventName); - } catch(err){ + } catch (err) { throw new ImperativeError({ msg: `Error unsubscribing from event: ${eventName}`, causeErrors: err }); } } diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 429ece0462..d47420cca6 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -35,7 +35,11 @@ export class EventUtils { */ public static getListOfApps(): string[] { const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); - const apps: string[] = []; + // We should not need to keep a reference to their sources + return ["Zowe", ...Object.keys(extendersJson.profileTypes)]; + + /* + const apps: string[] = ["Zowe"]; // default application name // Loop through each profile type and accumulate all names and their sources based on conditions. for (const [profileType, details] of Object.entries(extendersJson.profileTypes)) { // Check each entry in the 'from' array to decide if a tag is needed @@ -44,11 +48,13 @@ export class EventUtils { apps.push(profileType, "_vsce"); // tag indicating Visual Studio Code Extension } else if (item.includes("@zowe")) { apps.push(profileType); // no tag indicates Zowe CLI plugin (default) + } else { + apps.push(profileType + "_custom") // tag indicating a true Custom App } }); - apps.push("Zowe"); // default application name } return apps; + */ } /** @@ -57,8 +63,11 @@ export class EventUtils { * @static * @param {string} appName - The name of the application. */ - public static validateAppName(appName: string){ + public static validateAppName(appName: string) { const appList = this.getListOfApps(); + // Performing `appList.find(app => app.includes(appName))` will allow for "tags" (or suffixes) coming from `getListOfApps()` + // However, we do not want this behavior because it will allow partial application names to be used + // Hence why we should probably match the application name with the exact profileType in `extenders.json` if (appName !== "Zowe" && !appList.includes(appName)) { throw new ImperativeError({ msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` From 8b86ccfeca53d9dfc2fe798d8e25b72d31b71ab4 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:58:47 +0000 Subject: [PATCH 28/72] fix: changelog updates and codeql remarks on tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/CHANGELOG.md | 6 +++ .../imperative/src/config/src/ConfigUtils.ts | 2 +- .../imperative/src/config/src/ProfileInfo.ts | 6 +-- .../EventEmitter.integration.test.ts | 40 +++++++++---------- .../src/events/src/EventConstants.ts | 9 ----- .../src/events/src/EventOperator.ts | 16 ++++++-- .../imperative/src/events/src/EventUtils.ts | 11 ++--- .../utilities/npm-interface/install.ts | 4 +- .../utilities/npm-interface/uninstall.ts | 4 +- 9 files changed, 50 insertions(+), 48 deletions(-) diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 646ca839a6..7e3cb97192 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to the Imperative package will be documented in this file. ## Recent Changes - Enhancement: Add client-side custom-event handling capabilities. [#2136](https://github.com/zowe/zowe-cli/pull/2136) +- Next-Breaking: Refactored the Imperative Event Emitter class. [#2136](https://github.com/zowe/zowe-cli/pull/2136) + - Removed the `ImperativeEventEmitter` class. + - Added an `EventProcessor` class to handle event listening and emitting. + - Added an `EventOperator` class to handle creation and deletion of `EventProcessors`. + - Added an `EventUtils` class to contain all common utility methods for the Client Event Handling capabilities. + - Added `IEmitter`, `IWatcher`, and `IEmitterAndWatcher` interfaces to expose what application developers should see. ## `8.0.0-next.202405151329` diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 5e061dd265..373e69d8e9 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -47,7 +47,7 @@ export class ConfigUtils { * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal */ - public static readExtendersJsonFromDisk(): IExtendersJsonOpts { + public static readExtendersJson(): IExtendersJsonOpts { const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); if (!fsExistsSync(extenderJsonPath)) { jsonfile.writeFileSync(extenderJsonPath, { diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index bb19db3d06..85451b9149 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -828,7 +828,7 @@ export class ProfileInfo { }); } - this.mExtendersJson = ProfileInfo.readExtendersJsonFromDisk(); + this.mExtendersJson = ConfigUtils.readExtendersJson(); this.loadAllSchemas(); } @@ -1007,10 +1007,10 @@ export class ProfileInfo { * Reads the `extenders.json` file from the CLI home directory. * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal - * @deprecated Please use `ConfigUtils.readExtendersJsonFromDisk` instead + * @deprecated Please use `ConfigUtils.readExtendersJson` instead */ public static readExtendersJsonFromDisk(): IExtendersJsonOpts { - return ConfigUtils.readExtendersJsonFromDisk(); + return ConfigUtils.readExtendersJson(); } /** diff --git a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts index 7691a3d5fb..86f12f02bb 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts @@ -9,7 +9,7 @@ * */ -import { IImperativeEventJson as EventJson, EventUtils, IEventJson, ImperativeEventEmitter, ImperativeSharedEvents } from "../../.."; +import { EventOperator, EventTypes, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { TestLogger } from "../../../../__tests__/src/TestLogger"; import * as TestUtil from "../../../../__tests__/src/TestUtil"; @@ -18,9 +18,8 @@ import * as fs from "fs"; import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; -const iee = ImperativeEventEmitter; -const iee_s = ImperativeSharedEvents; let cwd = ''; +const appName = "Zowe"; describe("Event Emitter", () => { const mainModule = process.mainModule; @@ -38,50 +37,47 @@ describe("Event Emitter", () => { cwd = TEST_ENVIRONMENT.workingDir; }); - beforeEach(() => { - iee.initialize("zowe", { logger: testLogger }); - }); - - afterEach(() => { - iee.teardown(); - }); - afterAll(() => { process.mainModule = mainModule; TestUtil.rimraf(cwd); }); - const doesEventFileExists = (eventType: string) => { - const eventDir = iee.instance.getEventDir(eventType); + const doesEventFileExists = (eventName: string) => { + const eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : + EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.SharedEvents; + + const eventDir = EventUtils.getEventDir(eventType, appName); if (!fs.existsSync(eventDir)) return false; - if (fs.existsSync(path.join(eventDir, eventType))) return true; + if (fs.existsSync(path.join(eventDir, appName, eventName))) return true; return false; }; describe("Shared Events", () => { it("should create an event file upon first subscription if the file does not exist", () => { - const theEvent = iee_s.ON_CREDENTIAL_MANAGER_CHANGED; + const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const theProc = EventOperator.getZoweProcessor() expect(doesEventFileExists(theEvent)).toBeFalsy(); + expect((theProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); const subSpy = jest.fn(); - iee.instance.subscribe(theEvent, subSpy); + theProc.subscribeShared(theEvent, subSpy); expect(subSpy).not.toHaveBeenCalled(); expect(doesEventFileExists(theEvent)).toBeTruthy(); - expect(iee.instance.getEventContents(theEvent)).toBeFalsy(); - - iee.instance.emitEvent(theEvent); + theProc.emitEvent(theEvent); - (iee.instance as any).subscriptions.get(theEvent)[1][0](); // simulate FSWatcher called + (theProc as any).subscribedEvents.get(theEvent).subscriptions[0](); // simulate FSWatcher called expect(doesEventFileExists(theEvent)).toBeTruthy(); - const eventDetails: IEventJson = JSON.parse(iee.instance.getEventContents(theEvent)); + const eventDetails: IEventJson = (theProc as any).subscribedEvents.get(theEvent).toJson(); expect(eventDetails.eventName).toEqual(theEvent); - expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeTruthy(); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(subSpy).toHaveBeenCalled(); + + EventOperator.deleteProcessor(appName); }); it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); diff --git a/packages/imperative/src/events/src/EventConstants.ts b/packages/imperative/src/events/src/EventConstants.ts index 075527b89b..61641bdd20 100644 --- a/packages/imperative/src/events/src/EventConstants.ts +++ b/packages/imperative/src/events/src/EventConstants.ts @@ -10,7 +10,6 @@ */ -// TO DO - flesh out these enums to include all expected user and shared events export enum ZoweUserEvents { ON_VAULT_CHANGED = "onVaultChanged" } @@ -19,14 +18,6 @@ export enum ZoweSharedEvents { ON_CREDENTIAL_MANAGER_CHANGED = "onCredentialManagerChanged" } -export enum SharedEvents { - CUSTOM_SHARED_EVENT = "customSharedEvent" -} - -export enum UserEvents { - CUSTOM_USER_EVENT = "customUserEvent", -} - export enum EventTypes { ZoweUserEvents, ZoweSharedEvents, SharedEvents, UserEvents } export type EventCallback = () => void | PromiseLike; diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 3771f336fb..6fafa49f44 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -12,7 +12,6 @@ import { EventProcessor } from "./EventProcessor"; import { IEmitter, IEmitterAndWatcher, IProcessorTypes, IWatcher } from "./doc/IEventInstanceTypes"; import { Logger } from "../../logger"; -import { EventUtils } from "./EventUtils"; /** * @internal Interface for Zowe-specific event processing, combining emitter and watcher functionalities. @@ -46,13 +45,22 @@ export class EventOperator { const newInstance = new EventProcessor(appName, type, logger); this.instances.set(appName, newInstance); } - return this.instances.get(appName); + const procInstance = this.instances.get(appName); + if (procInstance.processorType !== type) { + procInstance.processorType = IProcessorTypes.BOTH; + // throw new ImperativeError({msg: "Not allowed to get the other hald"}) + } + return procInstance; } /** - * Retrieves a Zowe-specific event processor. + * Retrieves a Zowe-specific event processor. The purpose of this method is for internal + * Imperative APIs to get a properly initialized processor. This processor will be used + * when applications (like Zowe Explorer) call Imperative APIs that trigger events. For + * example, when the user updates credentials from Zowe Explorer, this processor will be + * used to emit an `OnVaultChanged` event. * - * @internal + * @internal Not meant to be called by application developers * @static * @returns {IZoweProcessor} The Zowe event processor instance. */ diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index d47420cca6..1c3fd9ee37 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -34,7 +34,7 @@ export class EventUtils { * @returns {string[]} List of application names. */ public static getListOfApps(): string[] { - const extendersJson = ConfigUtils.readExtendersJsonFromDisk(); + const extendersJson = ConfigUtils.readExtendersJson(); // We should not need to keep a reference to their sources return ["Zowe", ...Object.keys(extendersJson.profileTypes)]; @@ -147,11 +147,12 @@ export class EventUtils { * @return {IEventDisposable} An interface for managing the subscription. */ public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { - const dir = this.getEventDir(eventType, eeInst.appName); - this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), '.events')); - this.ensureEventsDirExists(join(ConfigUtils.getZoweDir(), dir)); + const dir = EventUtils.getEventDir(eventType, eeInst.appName); + const zoweDir = ConfigUtils.getZoweDir(); + this.ensureEventsDirExists(join(zoweDir, '.events')); + this.ensureEventsDirExists(join(zoweDir, dir)); - const filePath = join(ConfigUtils.getZoweDir(), dir, eventName); + const filePath = join(zoweDir, dir, eventName); this.ensureFileExists(filePath); const newEvent = new Event({ diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts index eff42b2adc..d8c02e7483 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts @@ -26,7 +26,7 @@ import { UpdateImpConfig } from "../../../UpdateImpConfig"; import { CredentialManagerOverride, ICredentialManagerNameMap } from "../../../../../security"; import { IProfileTypeConfiguration } from "../../../../../profiles"; import * as semver from "semver"; -import { ProfileInfo } from "../../../../../config"; +import { ConfigUtils, ProfileInfo } from "../../../../../config"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; // Helper function to update extenders.json object during plugin install. @@ -188,7 +188,7 @@ export async function install(packageLocation: string, registry: string, install // Only update global schema if we were able to load it from disk if (loadedSchema != null) { const existingTypes = loadedSchema.map((obj) => obj.type); - const extendersJson = ProfileInfo.readExtendersJsonFromDisk(); + const extendersJson = ConfigUtils.readExtendersJson(); // Determine new profile types to add to schema let shouldUpdate = false; diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts index a328bb160b..b400c1e5c5 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts @@ -19,7 +19,7 @@ import { ImperativeError } from "../../../../../error"; import { ExecUtils, TextUtils } from "../../../../../utilities"; import { StdioOptions } from "child_process"; import { findNpmOnPath } from "../NpmFunctions"; -import { ConfigSchema, ProfileInfo } from "../../../../../config"; +import { ConfigSchema, ConfigUtils, ProfileInfo } from "../../../../../config"; import { IProfileTypeConfiguration } from "../../../../../profiles"; const npmCmd = findNpmOnPath(); @@ -30,7 +30,7 @@ const npmCmd = findNpmOnPath(); * @returns A list of types to remove from the schema */ export const updateAndGetRemovedTypes = (npmPackage: string): string[] => { - const extendersJson = ProfileInfo.readExtendersJsonFromDisk(); + const extendersJson = ConfigUtils.readExtendersJson(); const pluginTypes = Object.keys(extendersJson.profileTypes) .filter((type) => extendersJson.profileTypes[type].from.includes(npmPackage)); const typesToRemove: string[] = []; From da91e26716c78f88432c1c0ccad94101209fde61 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 6 Jun 2024 20:21:27 +0000 Subject: [PATCH 29/72] chore: address PR feedback Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../EventEmitter.integration.test.ts | 2 +- packages/imperative/src/events/src/Event.ts | 2 +- .../imperative/src/events/src/EventUtils.ts | 25 ++++++++----------- .../src/events/src/doc/IEventJson.ts | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts index 86f12f02bb..fb2718e3a5 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts @@ -46,7 +46,7 @@ describe("Event Emitter", () => { const eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.SharedEvents; - const eventDir = EventUtils.getEventDir(eventType, appName); + const eventDir = EventUtils.getEventDir(appName); if (!fs.existsSync(eventDir)) return false; if (fs.existsSync(path.join(eventDir, appName, eventName))) return true; return false; diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index a4f435840e..7cd302b037 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -45,7 +45,7 @@ export class Event implements IEventJson { * * @returns {Object} The JSON representation of the event. */ - public toJson() { + public toJson(): IEventJson { return { eventTime: this.eventTime, eventName: this.eventName, diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 1c3fd9ee37..2881b2228b 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -17,6 +17,7 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { IEventDisposable } from "./doc"; import { Event } from "./Event"; import { EventProcessor } from "./EventProcessor"; +import { IO } from "../../io"; /** * A collection of helper functions related to event processing, including: @@ -63,16 +64,15 @@ export class EventUtils { * @static * @param {string} appName - The name of the application. */ - public static validateAppName(appName: string) { + public static validateAppName(appName: string): void { const appList = this.getListOfApps(); + if (appList.includes(appName)) return; // Performing `appList.find(app => app.includes(appName))` will allow for "tags" (or suffixes) coming from `getListOfApps()` // However, we do not want this behavior because it will allow partial application names to be used // Hence why we should probably match the application name with the exact profileType in `extenders.json` - if (appName !== "Zowe" && !appList.includes(appName)) { - throw new ImperativeError({ - msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` - }); - } + throw new ImperativeError({ + msg: `Application name not found: ${appName}. Please use an application name from the list:\n- ${appList.join("\n- ")}` + }); } /** @@ -98,14 +98,12 @@ export class EventUtils { /** * Determines the directory path for storing event files based on the event type and application name. * - * @param {EventTypes} eventType - The type of event. * @param {string} appName - The name of the application. * @return {string} The directory path. */ - public static getEventDir(eventType: EventTypes, appName: string): string { + public static getEventDir(appName: string): string { this.validateAppName(appName); - return eventType === EventTypes.SharedEvents || eventType === EventTypes.UserEvents ? - join(".events", appName) : ".events"; + return join(".events", appName); } /** @@ -116,7 +114,7 @@ export class EventUtils { public static ensureEventsDirExists(directoryPath: string) { try { if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath); + IO.mkdirp(directoryPath); } } catch (err) { throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); @@ -147,10 +145,9 @@ export class EventUtils { * @return {IEventDisposable} An interface for managing the subscription. */ public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { - const dir = EventUtils.getEventDir(eventType, eeInst.appName); const zoweDir = ConfigUtils.getZoweDir(); - this.ensureEventsDirExists(join(zoweDir, '.events')); - this.ensureEventsDirExists(join(zoweDir, dir)); + const dir = join(zoweDir, EventUtils.getEventDir(eeInst.appName)); + this.ensureEventsDirExists(dir); const filePath = join(zoweDir, dir, eventName); this.ensureFileExists(filePath); diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 5189be5733..65cc7d9d03 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -41,5 +41,5 @@ export interface IEventJson { /** * List of watchers to eventually close */ - subscriptions: fs.FSWatcher[]; + subscriptions?: fs.FSWatcher[]; } From b8a2af66d1428fff6f2fae12376ce3d4c81ab86a Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:33:29 +0000 Subject: [PATCH 30/72] fix: don't append the root path twice Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/EventUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 2881b2228b..e0ae5314e6 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -149,7 +149,7 @@ export class EventUtils { const dir = join(zoweDir, EventUtils.getEventDir(eeInst.appName)); this.ensureEventsDirExists(dir); - const filePath = join(zoweDir, dir, eventName); + const filePath = join(dir, eventName); this.ensureFileExists(filePath); const newEvent = new Event({ From c6264f93ff6729fc4863960759c1a5adda6e8308 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:50:47 +0000 Subject: [PATCH 31/72] fix: backwards logic for zowe v. custom events Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/EventProcessor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 0ee7e8a01b..1b014834b8 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -66,8 +66,8 @@ export class EventProcessor { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } - const isCustom = EventUtils.isSharedEvent(eventName); - const eventType = isCustom ? EventTypes.SharedEvents : EventTypes.ZoweSharedEvents; + const isZoweEvent = EventUtils.isSharedEvent(eventName); + const eventType = isZoweEvent ? EventTypes.ZoweSharedEvents : EventTypes.SharedEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); return disposable; @@ -84,8 +84,8 @@ export class EventProcessor { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } - const isCustom = EventUtils.isUserEvent(eventName); - const eventType = isCustom ? EventTypes.UserEvents : EventTypes.ZoweUserEvents; + const isZoweEvent = EventUtils.isUserEvent(eventName); + const eventType = isZoweEvent ? EventTypes.ZoweUserEvents : EventTypes.UserEvents; const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); return disposable; From ca664a1e69136111c4fea1cf5b55e37c072087f1 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:02:15 +0000 Subject: [PATCH 32/72] fix: eventTimes never defined Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/EventProcessor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 1b014834b8..1edeb66396 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -44,6 +44,7 @@ export class EventProcessor { EventUtils.validateAppName(appName); this.subscribedEvents = new Map(); + this.eventTimes = new Map(); this.appName = appName; this.processorType = type; From 6212193f84a2f31bc5c5fcda4080f673930d4125 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 10 Jun 2024 09:59:27 -0400 Subject: [PATCH 33/72] tests that I didn't push from last week Signed-off-by: Amber Torrise --- .../__unit__/EventOperator.unit.test.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts new file mode 100644 index 0000000000..5611036f4a --- /dev/null +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -0,0 +1,124 @@ +/* +* 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 { EventOperator } from '../../src/EventOperator'; +import { EventProcessor } from '../../src/EventProcessor'; +import { Logger } from '../../..'; +import { EventUtils } from '../../src/EventUtils'; +import { IEventDisposable, IProcessorTypes } from '../../src/doc'; +import { FSWatcher } from 'fs'; +import { Event } from '../../..'; +import { EventTypes } from "../../src/EventConstants"; + +jest.mock('../../src/EventProcessor'); +jest.mock('../../../Logger'); + +const logger = Logger.getImperativeLogger(); +const validateAppNameSpy = jest.spyOn(EventUtils, 'validateAppName'); +const createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription'); +const setupWatcherSpy = jest.spyOn(EventUtils, 'setupWatcher'); +const getListOfAppsSpy = jest.spyOn(EventUtils, 'getListOfApps'); +// const writeEventSpy = jest.spyOn(EventUtils, 'writeEvent'); + +describe('EventOperator Unit Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + createSubscriptionSpy.mockImplementation(() => ({ + close: jest.fn() + } as IEventDisposable)); + + setupWatcherSpy.mockImplementation(() => ({ + close: jest.fn(), + removeAllListeners: jest.fn() + } as unknown as FSWatcher)); + }); + + describe("processor tests", () => { + it("createProcessor should create a new EventProcessor if not already existing", () => { + const appName = 'TestApp'; + const type = IProcessorTypes.BOTH; + + getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); + const processor = EventOperator.getProcessor(appName, logger); + + expect(validateAppNameSpy).toHaveBeenCalledWith(appName); + expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("getZoweProcessor should return the Zowe processor instance", () => { + const processor = EventOperator.getZoweProcessor(); + + expect(validateAppNameSpy).toHaveBeenCalledWith("Zowe"); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("getProcessor should return a generic event processor", () => { + const appName = 'GenericApp'; + + getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); + const processor = EventOperator.getProcessor(appName, logger); + + expect(validateAppNameSpy).toHaveBeenCalledWith(appName); + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("deleteProcessor should remove the correct event processor", () => { + const appName = 'DeleteApp'; + + getListOfAppsSpy.mockImplementation(() => [appName]); + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.SharedEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.getProcessor(appName); + EventOperator.deleteProcessor(appName); + + expect(validateAppNameSpy).toHaveBeenCalledWith(appName); + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("watcher tests", () => { + it("getWatcher should return a watcher-only event processor", () => { + const appName = 'WatcherApp'; + const processor = EventOperator.getWatcher(appName, logger); + + expect(validateAppNameSpy).toHaveBeenCalledWith(appName); + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + }); + + describe("emitter tests", () => { + it("getEmitter should return an emitter-only event processor", () => { + const appName = 'EmitterApp'; + const processor = EventOperator.getEmitter(appName, logger); + + expect(validateAppNameSpy).toHaveBeenCalledWith(appName); + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + }); +}); \ No newline at end of file From 22313f5d036b379b66c7837f56e8b652df44b005 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 10 Jun 2024 14:17:06 +0000 Subject: [PATCH 34/72] fix: make eventType optional Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- packages/imperative/src/events/src/Event.ts | 2 +- .../src/events/src/EventProcessor.ts | 4 +- .../imperative/src/events/src/EventUtils.ts | 53 ++++++++++++++----- .../src/events/src/doc/IEventJson.ts | 8 +-- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/packages/imperative/src/events/src/Event.ts b/packages/imperative/src/events/src/Event.ts index 7cd302b037..d316e215c9 100644 --- a/packages/imperative/src/events/src/Event.ts +++ b/packages/imperative/src/events/src/Event.ts @@ -34,7 +34,7 @@ export class Event implements IEventJson { constructor({ eventTime, eventName, eventType, appName, eventFilePath, subscriptions }: IEventJson) { this.eventTime = eventTime; this.eventName = eventName; - this.eventType = eventType; + this.eventType = eventType ?? null; this.appName = appName; this.eventFilePath = eventFilePath; this.subscriptions = subscriptions; diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 1edeb66396..07d6dfc1fe 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -106,7 +106,7 @@ export class EventProcessor { throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}` }); } try { - const event = this.subscribedEvents.get(eventName); + const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); event.eventTime = new Date().toISOString(); EventUtils.writeEvent(event); } catch (err) { @@ -126,7 +126,7 @@ export class EventProcessor { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } try { - const event = this.subscribedEvents.get(eventName); + const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); event.eventTime = new Date().toISOString(); EventUtils.writeEvent(event); } catch (err) { diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index e0ae5314e6..a62ad60e66 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -14,7 +14,7 @@ import { join } from "path"; import { ZoweUserEvents, ZoweSharedEvents, EventTypes, EventCallback } from "./EventConstants"; import * as fs from "fs"; import { ConfigUtils } from "../../config/src/ConfigUtils"; -import { IEventDisposable } from "./doc"; +import { IEventDisposable, IEventJson } from "./doc"; import { Event } from "./Event"; import { EventProcessor } from "./EventProcessor"; import { IO } from "../../io"; @@ -95,6 +95,21 @@ export class EventUtils { return Object.values(ZoweSharedEvents).includes(eventName); } + /** + * Retrieve the event contents form disk + * + * @internal This is not intended for application developers + * @param eventFilePath The path to the event file + * @returns The object representing the Event + */ + public static getEventContents(eventFilePath: string): IEventJson { + try { + return JSON.parse(fs.readFileSync(eventFilePath).toString()); + } catch (err) { + throw new ImperativeError({msg: `Unable to retrieve event contents: Path: ${eventFilePath}`}); + } + } + /** * Determines the directory path for storing event files based on the event type and application name. * @@ -136,33 +151,44 @@ export class EventUtils { } } + /** - * Creates and registers a new event subscription for a specific event processor. + * Create an event with minimal information * - * @param {EventProcessor} eeInst - The event processor instance. - * @param {string} eventName - The name of the event. - * @param {EventTypes} eventType - The type of event. - * @return {IEventDisposable} An interface for managing the subscription. + * @internal This is not intended for application developers + * @param eventName The name of the event. + * @param appName The name of the application. + * @returns The created event */ - public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { + public static createEvent(eventName: string, appName: string): Event { const zoweDir = ConfigUtils.getZoweDir(); - const dir = join(zoweDir, EventUtils.getEventDir(eeInst.appName)); + const dir = join(zoweDir, EventUtils.getEventDir(appName)); this.ensureEventsDirExists(dir); const filePath = join(dir, eventName); this.ensureFileExists(filePath); - const newEvent = new Event({ + return new Event({ eventTime: new Date().toISOString(), eventName: eventName, - eventType: eventType, - appName: eeInst.appName, + appName: appName, eventFilePath: filePath, - subscriptions: [] + subscriptions: [], }); + } + /** + * Creates and registers a new event subscription for a specific event processor. + * + * @param {EventProcessor} eeInst - The event processor instance. + * @param {string} eventName - The name of the event. + * @param {EventTypes} eventType - The type of event. + * @return {IEventDisposable} An interface for managing the subscription. + */ + public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { + const newEvent = EventUtils.createEvent(eventName, eeInst.appName); + newEvent.eventType = eventType; eeInst.subscribedEvents.set(eventName, newEvent); - return { close: () => eeInst.unsubscribe(eventName) }; @@ -181,6 +207,7 @@ export class EventUtils { const watcher = fs.watch(event.eventFilePath, (trigger: "rename" | "change") => { // Accommodates for the delay between actual file change event and fsWatcher's perception //(Node.JS triggers this notification event 3 times) + event.eventTime = EventUtils.getEventContents(event.eventFilePath).eventTime; if (eeInst.eventTimes.get(eventName) !== event.eventTime) { eeInst.logger.debug(`EventEmitter: Event "${trigger}" emitted: ${eventName}`); if (Array.isArray(callbacks)) { diff --git a/packages/imperative/src/events/src/doc/IEventJson.ts b/packages/imperative/src/events/src/doc/IEventJson.ts index 65cc7d9d03..85bbe6363c 100644 --- a/packages/imperative/src/events/src/doc/IEventJson.ts +++ b/packages/imperative/src/events/src/doc/IEventJson.ts @@ -26,10 +26,6 @@ export interface IEventJson { * The name of event that occurred */ eventName: string; - /** - * The type of event that occurred - */ - eventType: EventTypes; /** * The application name that triggered the event */ @@ -38,6 +34,10 @@ export interface IEventJson { * The file path for information on the emitted event */ eventFilePath: string; + /** + * The type of event that occurred + */ + eventType?: EventTypes; /** * List of watchers to eventually close */ From 26c8c967e557fab185eee876f27d912ad359eb3e Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 10 Jun 2024 13:26:41 -0400 Subject: [PATCH 35/72] completed unit tests for EventOperator Signed-off-by: Amber Torrise --- .../__unit__/EventOperator.unit.test.ts | 87 ++++++++++++------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index 5611036f4a..d0c378127c 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -13,8 +13,7 @@ import { EventOperator } from '../../src/EventOperator'; import { EventProcessor } from '../../src/EventProcessor'; import { Logger } from '../../..'; import { EventUtils } from '../../src/EventUtils'; -import { IEventDisposable, IProcessorTypes } from '../../src/doc'; -import { FSWatcher } from 'fs'; +import { IProcessorTypes } from '../../src/doc'; import { Event } from '../../..'; import { EventTypes } from "../../src/EventConstants"; @@ -22,61 +21,46 @@ jest.mock('../../src/EventProcessor'); jest.mock('../../../Logger'); const logger = Logger.getImperativeLogger(); -const validateAppNameSpy = jest.spyOn(EventUtils, 'validateAppName'); -const createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription'); -const setupWatcherSpy = jest.spyOn(EventUtils, 'setupWatcher'); const getListOfAppsSpy = jest.spyOn(EventUtils, 'getListOfApps'); -// const writeEventSpy = jest.spyOn(EventUtils, 'writeEvent'); -describe('EventOperator Unit Tests', () => { +describe("EventOperator Unit Tests", () => { beforeEach(() => { jest.clearAllMocks(); - createSubscriptionSpy.mockImplementation(() => ({ - close: jest.fn() - } as IEventDisposable)); - - setupWatcherSpy.mockImplementation(() => ({ - close: jest.fn(), - removeAllListeners: jest.fn() - } as unknown as FSWatcher)); }); describe("processor tests", () => { - it("createProcessor should create a new EventProcessor if not already existing", () => { + it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { const appName = 'TestApp'; const type = IProcessorTypes.BOTH; getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); const processor = EventOperator.getProcessor(appName, logger); - expect(validateAppNameSpy).toHaveBeenCalledWith(appName); expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); expect(processor).toBeInstanceOf(EventProcessor); }); - it("getZoweProcessor should return the Zowe processor instance", () => { + it("'getZoweProcessor' should return the Zowe processor instance", () => { const processor = EventOperator.getZoweProcessor(); - expect(validateAppNameSpy).toHaveBeenCalledWith("Zowe"); + // expect(validateAppNameSpy).toHaveBeenCalledWith("Zowe"); expect(processor).toBeInstanceOf(EventProcessor); }); - it("getProcessor should return a generic event processor", () => { + it("'getProcessor' should return a generic event processor", () => { const appName = 'GenericApp'; getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); const processor = EventOperator.getProcessor(appName, logger); - expect(validateAppNameSpy).toHaveBeenCalledWith(appName); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); expect(processor).toBeInstanceOf(EventProcessor); }); - it("deleteProcessor should remove the correct event processor", () => { + it("'deleteProcessor' should remove the correct event processor", () => { const appName = 'DeleteApp'; - - getListOfAppsSpy.mockImplementation(() => [appName]); const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ ['testEvent', { eventTime: '', @@ -92,33 +76,76 @@ describe('EventOperator Unit Tests', () => { } as unknown as Event] ]); - EventOperator.getProcessor(appName); EventOperator.deleteProcessor(appName); - expect(validateAppNameSpy).toHaveBeenCalledWith(appName); expect(EventOperator['instances'].has(appName)).toBe(false); }); }); describe("watcher tests", () => { - it("getWatcher should return a watcher-only event processor", () => { + it("'getWatcher' should return a watcher-only event processor", () => { const appName = 'WatcherApp'; const processor = EventOperator.getWatcher(appName, logger); - expect(validateAppNameSpy).toHaveBeenCalledWith(appName); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); expect(processor).toBeInstanceOf(EventProcessor); }); + it("'deleteWatcher' should remove the correct event processor", () => { + const appName = 'DeleteWatcher'; + const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); + + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteWatcher(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); }); describe("emitter tests", () => { - it("getEmitter should return an emitter-only event processor", () => { + it("'getEmitter' should return an emitter-only event processor", () => { const appName = 'EmitterApp'; const processor = EventOperator.getEmitter(appName, logger); - expect(validateAppNameSpy).toHaveBeenCalledWith(appName); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); expect(processor).toBeInstanceOf(EventProcessor); }); + + it("'deleteEmitter' should remove the correct event processor", () => { + const appName = 'DeleteEmitter'; + const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); + + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteEmitter(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); }); }); \ No newline at end of file From 180bfc1edeb48c6fb94583856e9e70b2c64f3ae0 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 10 Jun 2024 13:33:44 -0400 Subject: [PATCH 36/72] removing uneceesary spies from EventOperator Signed-off-by: Amber Torrise --- .../__tests__/__unit__/EventOperator.unit.test.ts | 10 ---------- .../__tests__/__unit__/EventProcessor.unit.test.ts | 0 2 files changed, 10 deletions(-) create mode 100644 packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index d0c378127c..f0b6d649b1 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -12,7 +12,6 @@ import { EventOperator } from '../../src/EventOperator'; import { EventProcessor } from '../../src/EventProcessor'; import { Logger } from '../../..'; -import { EventUtils } from '../../src/EventUtils'; import { IProcessorTypes } from '../../src/doc'; import { Event } from '../../..'; import { EventTypes } from "../../src/EventConstants"; @@ -21,7 +20,6 @@ jest.mock('../../src/EventProcessor'); jest.mock('../../../Logger'); const logger = Logger.getImperativeLogger(); -const getListOfAppsSpy = jest.spyOn(EventUtils, 'getListOfApps'); describe("EventOperator Unit Tests", () => { beforeEach(() => { @@ -32,8 +30,6 @@ describe("EventOperator Unit Tests", () => { it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { const appName = 'TestApp'; const type = IProcessorTypes.BOTH; - - getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); const processor = EventOperator.getProcessor(appName, logger); expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); @@ -43,14 +39,11 @@ describe("EventOperator Unit Tests", () => { it("'getZoweProcessor' should return the Zowe processor instance", () => { const processor = EventOperator.getZoweProcessor(); - // expect(validateAppNameSpy).toHaveBeenCalledWith("Zowe"); expect(processor).toBeInstanceOf(EventProcessor); }); it("'getProcessor' should return a generic event processor", () => { const appName = 'GenericApp'; - - getListOfAppsSpy.mockImplementation(() => ["Zowe", appName]); const processor = EventOperator.getProcessor(appName, logger); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); @@ -60,7 +53,6 @@ describe("EventOperator Unit Tests", () => { it("'deleteProcessor' should remove the correct event processor", () => { const appName = 'DeleteApp'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); - processor.subscribedEvents = new Map([ ['testEvent', { eventTime: '', @@ -93,7 +85,6 @@ describe("EventOperator Unit Tests", () => { it("'deleteWatcher' should remove the correct event processor", () => { const appName = 'DeleteWatcher'; const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); - processor.subscribedEvents = new Map([ ['testEvent', { eventTime: '', @@ -127,7 +118,6 @@ describe("EventOperator Unit Tests", () => { it("'deleteEmitter' should remove the correct event processor", () => { const appName = 'DeleteEmitter'; const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); - processor.subscribedEvents = new Map([ ['testEvent', { eventTime: '', diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts new file mode 100644 index 0000000000..e69de29bb2 From e6760b248f9fe57f251958799515f0b3468ef9f1 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 10 Jun 2024 13:50:27 -0400 Subject: [PATCH 37/72] wip - eventProcessorUnitTest Signed-off-by: Amber Torrise --- .../__unit__/EventProcessor.unit.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index e69de29bb2..398b896c0e 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -0,0 +1,29 @@ +import { EventProcessor } from '../../src/EventProcessor'; +import { Logger } from '../../../logger/src/Logger'; +import { IProcessorTypes } from '../../src/doc/IEventInstanceTypes'; +import { EventOperator } from '../..'; + +jest.mock('../../../logger/src/Logger'); +jest.mock('../../src/EventUtils'); +jest.mock('../../../error/src/ImperativeError'); + +describe('EventProcessor Unit Tests', () => { + const appName = 'TestApp'; + const logger = Logger.getImperativeLogger(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('constructor initializes EventProcessor correctly', () => { + // need to fix :/ + expect(EventOperator['instances'].get(appName)).toBeUndefined(); + + const type = IProcessorTypes.BOTH; + const processor = new EventProcessor(appName, type, logger); + + expect(EventOperator['instances'].get(appName)).toBeDefined(); + expect(processor.appName).toBe(appName); + expect(processor.processorType).toBe(type); + }); +}); From 11f3bf1bd4971b6ef7debe93e9712ab19ba7faa1 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:59:42 +0000 Subject: [PATCH 38/72] fix: other tests related to old event emitter Current state: Test Suites: 8 failed, 424 passed, 432 total Tests: 26 failed, 1 skipped, 3285 passed, 3312 total Snapshots: 1167 passed, 1167 total Time: 150.011 s Note: There are files being created out of the EventOperator unit-tests - packages/imperative/src/security/__tests__/.events/ - packages/imperative/src/security/__tests__/extenders.json Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/CommandProcessor.unit.test.ts | 1 - .../config/__tests__/Config.api.unit.test.ts | 2 - .../__tests__/Config.secure.unit.test.ts | 5 +- .../src/config/__tests__/Config.unit.test.ts | 2 - .../__tests__/ConfigAutoStore.unit.test.ts | 1 - .../ProfileInfo.TeamConfig.unit.test.ts | 5 +- .../__resources__/project.config.json | 2 +- .../__unit__/EventEmitter.unit.test.ts | 285 ------------------ .../cmd/import/import.handler.unit.test.ts | 1 - .../config/cmd/init/init.handler.unit.test.ts | 2 +- .../cmd/secure/secure.handler.unit.test.ts | 7 +- .../BaseAuthHandler.config.unit.test.ts | 5 +- .../ConnectionPropsForSessCfg.unit.test.ts | 1 - .../CredentialManagerOverride.unit.test.ts | 6 +- 14 files changed, 13 insertions(+), 312 deletions(-) delete mode 100644 packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts diff --git a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts index 104dddc311..aaaa738106 100644 --- a/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts +++ b/packages/imperative/src/cmd/__tests__/CommandProcessor.unit.test.ts @@ -29,7 +29,6 @@ import { join } from "path"; jest.mock("../src/syntax/SyntaxValidator"); jest.mock("../src/utils/SharedOptions"); jest.mock("../../utilities/src/ImperativeConfig"); -jest.mock("../../events/src/ImperativeEventEmitter"); // Persist the original definitions of process.write const ORIGINAL_STDOUT_WRITE = process.stdout.write; diff --git a/packages/imperative/src/config/__tests__/Config.api.unit.test.ts b/packages/imperative/src/config/__tests__/Config.api.unit.test.ts index 8130b143c0..af72547cbb 100644 --- a/packages/imperative/src/config/__tests__/Config.api.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.api.unit.test.ts @@ -19,8 +19,6 @@ import { IConfig } from "../src/doc/IConfig"; import { IConfigLayer } from "../src/doc/IConfigLayer"; import { IConfigProfile } from "../src/doc/IConfigProfile"; -jest.mock("../../events/src/ImperativeEventEmitter"); - const MY_APP = "my_app"; const mergeConfig: IConfig = { diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index bd0f795db6..82374551dc 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -18,7 +18,7 @@ import { Config } from "../src/Config"; import { IConfig } from "../src/doc/IConfig"; import { IConfigSecure } from "../src/doc/IConfigSecure"; import { IConfigVault } from "../src/doc/IConfigVault"; -import { ImperativeEventEmitter } from "../../events"; +import { EventOperator } from "../../events"; const MY_APP = "my_app"; @@ -47,8 +47,7 @@ describe("Config secure tests", () => { }); beforeEach(() => { - jest.spyOn(ImperativeEventEmitter, "initialize").mockImplementation(); - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }}); + // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); mockSecureLoad = jest.fn(); mockSecureSave = jest.fn(); diff --git a/packages/imperative/src/config/__tests__/Config.unit.test.ts b/packages/imperative/src/config/__tests__/Config.unit.test.ts index abfb7480d5..1ee1928519 100644 --- a/packages/imperative/src/config/__tests__/Config.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.unit.test.ts @@ -20,8 +20,6 @@ import * as JSONC from "comment-json"; import { ConfigLayers, ConfigSecure } from "../src/api"; -jest.mock("../../events/src/ImperativeEventEmitter"); - const MY_APP = "my_app"; describe("Config tests", () => { diff --git a/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts b/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts index 73a22e7908..a60a211ce3 100644 --- a/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigAutoStore.unit.test.ts @@ -10,7 +10,6 @@ */ jest.mock("../../logger/src/LoggerUtils"); -jest.mock("../../events/src/ImperativeEventEmitter"); import { AbstractAuthHandler } from "../../imperative"; import { SessConstants } from "../../rest"; diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index e8db53d543..ea5a9e92c3 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -33,9 +33,8 @@ import { ConfigUtils } from "../src/ConfigUtils"; import { ConfigProfiles } from "../src/api"; import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; -import { Logger } from "../.."; +import { Logger } from "../../logger/src/Logger"; -jest.mock("../../events/src/ImperativeEventEmitter"); const testAppNm = "ProfInfoApp"; const testEnvPrefix = testAppNm.toUpperCase(); @@ -905,7 +904,7 @@ describe("TeamConfig ProfileInfo tests", () => { } expect(unexpectedError).toBeUndefined(); } else { - expect("Profile " + desiredProfile + "not found").toBeUndefined(); + expect("Profile " + desiredProfile + " not found").toBeUndefined(); } expect(mergedArgs.missingArgs.find(a => a.argName === "user")?.secure).toBeTruthy(); expect(mergedArgs.missingArgs.find(a => a.argName === "password")?.secure).toBeTruthy(); diff --git a/packages/imperative/src/config/__tests__/__resources__/project.config.json b/packages/imperative/src/config/__tests__/__resources__/project.config.json index c2a1b3be7f..5a1f5b485f 100644 --- a/packages/imperative/src/config/__tests__/__resources__/project.config.json +++ b/packages/imperative/src/config/__tests__/__resources__/project.config.json @@ -43,4 +43,4 @@ }, "plugins": [], "autoStore": true -} +} \ No newline at end of file diff --git a/packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts deleted file mode 100644 index ff7ba8fef7..0000000000 --- a/packages/imperative/src/events/__tests__/__unit__/EventEmitter.unit.test.ts +++ /dev/null @@ -1,285 +0,0 @@ -/* -* 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 * as fs from "fs"; -import { join } from "path"; -import { homedir } from "os"; -import { Logger } from "../../../logger/src/Logger"; -import { ImperativeEventEmitter, ImperativeSharedEvents, ImperativeUserEvents } from "../.."; - -jest.mock("fs"); - -describe("Event Emitter", () => { - const iee = ImperativeEventEmitter; - const sharedDir = join(__dirname, ".zowe", ".events"); - const userDir = join(homedir(), ".zowe", ".events"); - let spyFsWriteFileSync: jest.SpyInstance; - let allCallbacks: Function[]; - let removeAllListeners: jest.SpyInstance; - const closeWatcher = jest.fn(); - - beforeEach(() => { - jest.restoreAllMocks(); - (iee as any).initialized = undefined; - process.env["ZOWE_CLI_HOME"] = join(__dirname, ".zowe"); - jest.spyOn(fs, "existsSync").mockImplementation(jest.fn()); - jest.spyOn(fs, "mkdirSync").mockImplementation(jest.fn()); - jest.spyOn(fs, "openSync").mockImplementation(jest.fn()); - jest.spyOn(fs, "closeSync").mockImplementation(jest.fn()); - jest.spyOn(fs, "openSync").mockImplementation(jest.fn()); - spyFsWriteFileSync = jest.spyOn(fs, "writeFileSync").mockImplementation(jest.fn()); - allCallbacks = []; - removeAllListeners = jest.fn().mockReturnValue({ close: closeWatcher }); - jest.spyOn(fs, "watch").mockImplementation((_event: string | any, cb: Function | any) => { - allCallbacks.push(cb); - return { close: jest.fn(), removeAllListeners } as any; - }); - }); - - describe("Base structure and emission", () => { - it("should only allow for one instance of the event emitter", () => { - const mockLogger: any = { warn: jest.fn() as any }; - iee.initialize("test", { logger: mockLogger }); - iee.initialize("dummy", { logger: mockLogger }); - expect(mockLogger.warn).toHaveBeenCalledTimes(1); - expect(mockLogger.warn.mock.calls[0][0]).toContain("Only one instance"); - expect(iee.instance.appName).toEqual("test"); - expect(iee.instance.logger).toEqual(mockLogger); - }); - - it("should determine the type of event", () => { - iee.initialize("test"); - expect(iee.instance.isUserEvent("dummy")).toBe(false); - expect(iee.instance.isUserEvent(ImperativeUserEvents.ON_VAULT_CHANGED)).toBe(true); - expect(iee.instance.isSharedEvent("dummy")).toBe(false); - expect(iee.instance.isSharedEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toBe(true); - - expect(iee.instance.isCustomEvent(ImperativeUserEvents.ON_VAULT_CHANGED)).toBe(false); - expect(iee.instance.isCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toBe(false); - expect(iee.instance.isCustomEvent("dummy")).toBe(true); - }); - - it("should determine the correct directory based on the event", () => { - iee.initialize("test"); - expect(iee.instance.getEventDir("dummy")).toEqual(sharedDir); - expect(iee.instance.getEventDir(ImperativeUserEvents.ON_VAULT_CHANGED)).toEqual(userDir); - expect(iee.instance.getEventDir(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toEqual(sharedDir); - delete process.env["ZOWE_CLI_HOME"]; - }); - - it("should not allow all kinds of events to be emitted", () => { - iee.initialize("zowe"); - expect(iee.instance.appName).toEqual("zowe"); - - const processError = (eventType: string, msg: string, isCustomEvent = true) => { - let caughtError: any; - try { - iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](eventType as any); - } catch (err) { - caughtError = err; - } - expect(caughtError).toBeDefined(); - expect(caughtError.message).toContain(msg); - }; - - const aMsg = "Unable to determine the type of event."; - const bMsg = "Operation not allowed. Event is considered protected"; - - // Application developers shouldn't be able to emit custom events from emitEvent, even though it is an internal method - processError("dummy", aMsg, false); - processError(ImperativeUserEvents.ON_VAULT_CHANGED, bMsg); - processError(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, bMsg); - }); - - it("should write to a file with all required properties in IImperativeEventJson to the correct location", () => { - iee.initialize("zowe"); - expect(iee.instance.appName).toEqual("zowe"); - - const processEvent = (theEvent: any, isUser: boolean, isCustomEvent = false) => { - // Emit the event - iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](theEvent); - - const dir = isUser ? userDir : sharedDir; - expect(fs.existsSync).toHaveBeenCalledWith(dir); - expect(fs.mkdirSync).toHaveBeenCalledWith(dir); - expect(spyFsWriteFileSync.mock.calls[0][0]).toEqual(join(dir, theEvent)); - expect(JSON.parse(spyFsWriteFileSync.mock.calls[0][1])).toMatchObject({ - type: theEvent, - user: isUser, - loc: dir, - }); - spyFsWriteFileSync.mockClear(); - }; - - processEvent(ImperativeUserEvents.ON_VAULT_CHANGED, true); - processEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); - processEvent("onSuperCustomEvent", false, true); - }); - - it("should fail to emit, subscribe or unsubscribe if the emitter has not been initialized", () => { - const getError = (shouldThrow: any) => { - let caughtError: any; - try { - shouldThrow(); - } catch (err) { - caughtError = err; - } - return caughtError ?? { message: "THIS METHOD DID NOT THROW AN ERROR" }; - }; - - const cbs = [ - // Emitting should fail if IEE is not initialized - () => { iee.instance.emitEvent("dummy" as any); }, - () => { iee.instance.emitEvent(ImperativeUserEvents.ON_VAULT_CHANGED); }, - () => { iee.instance.emitEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, - () => { iee.instance.emitCustomEvent("dummy"); }, - () => { iee.instance.emitCustomEvent(ImperativeUserEvents.ON_VAULT_CHANGED); }, - () => { iee.instance.emitCustomEvent(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, - - // Subscribing should fail if IEE is not initialized - () => { iee.instance.subscribe("dummy", jest.fn()); }, - () => { iee.instance.subscribe(ImperativeUserEvents.ON_VAULT_CHANGED, jest.fn()); }, - () => { iee.instance.subscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, jest.fn()); }, - () => { iee.instance.unsubscribe("dummy"); }, - () => { iee.instance.unsubscribe(ImperativeUserEvents.ON_VAULT_CHANGED); }, - () => { iee.instance.unsubscribe(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); }, - ]; - cbs.forEach(cb => { - expect((getError(cb)).message).toContain("You must initialize the instance"); - }); - }); - - it("should surface errors if unable to create event files or directories", () => { - iee.initialize("zowe"); - - jest.spyOn(fs, "mkdirSync").mockImplementationOnce(() => { throw "DIR"; }); - - const theEvent = ImperativeUserEvents.ON_VAULT_CHANGED; - try { - iee.instance.subscribe(theEvent, jest.fn()); - } catch (err) { - expect(err.message).toContain("Unable to create '.events' directory."); - } - expect(fs.existsSync).toHaveBeenCalledWith(userDir); - expect(fs.mkdirSync).toHaveBeenCalledWith(userDir); - - jest.spyOn(fs, "closeSync").mockImplementation(() => { throw "FILE"; }); - - try { - iee.instance.subscribe(theEvent, jest.fn()); - } catch (err) { - expect(err.message).toContain("Unable to create event file."); - } - expect(fs.existsSync).toHaveBeenCalledWith(join(userDir, theEvent)); - expect(fs.openSync).toHaveBeenCalledWith(join(userDir, theEvent), "w"); - expect(fs.closeSync).toHaveBeenCalled(); - }); - - it("should subscribe even when the onEventFile or the events directory do not exist", () => { - iee.initialize("zowe"); - expect(iee.instance.appName).toEqual("zowe"); - - const processSubcription = (theEvent: any, isUser: boolean) => { - const dir = isUser ? userDir : sharedDir; - const cbSpy = jest.fn(); - iee.instance.subscribe(theEvent, cbSpy); - - // Ensure the directory is created - expect(fs.existsSync).toHaveBeenCalledWith(dir); - expect(fs.mkdirSync).toHaveBeenCalledWith(dir); - - // Ensure the file is created - expect(fs.existsSync).toHaveBeenCalledWith(join(dir, theEvent)); - expect(fs.openSync).toHaveBeenCalledWith(join(dir, theEvent), "w"); - expect(fs.closeSync).toHaveBeenCalled(); - }; - - processSubcription(ImperativeUserEvents.ON_VAULT_CHANGED, true); - processSubcription(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED, false); - }); - - it("should trigger all callbacks when subscribed event is emitted", () => { - jest.spyOn(ImperativeEventEmitter.prototype, "emitEvent").mockImplementation((theEvent: any) => { - (iee.instance as any).subscriptions.get(theEvent)[1].forEach((cb: any) => cb()); - }); - jest.spyOn(fs, "readFileSync").mockReturnValue("{\"time\":\"123456\"}"); - - iee.initialize("zowe"); - expect(iee.instance.appName).toEqual("zowe"); - - const processEmission = (theEvent: any, isCustomEvent = false) => { - const cbSpy = jest.fn().mockReturnValue("test"); - const numberOfCalls = Math.floor(Math.random() * 20); - let i = numberOfCalls; - while(i-- > 0) { - iee.instance.subscribe(theEvent, cbSpy); - } - - iee.instance[(isCustomEvent ? "emitCustomEvent" : "emitEvent")](theEvent); - expect(cbSpy).toHaveBeenCalledTimes(numberOfCalls); - }; - - processEmission(ImperativeUserEvents.ON_VAULT_CHANGED); - processEmission(ImperativeSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED); - }); - - it("should unsubscribe from events successfully", () => { - iee.initialize("zowe"); - - const dummyMap = { - has: () => (true), - delete: jest.fn(), - get: () => ([{ removeAllListeners }, jest.fn()]) - }; - // Mocked map of subscriptions - (iee.instance as any).subscriptions = dummyMap; - (iee.instance as any).eventTimes = dummyMap; - - iee.instance.unsubscribe("dummy"); - expect(closeWatcher).toHaveBeenCalled(); - }); - - it("should teardown the Event Emitter instance successfully", () => { - expect((iee as any).initialized).toBeFalsy(); - iee.teardown(); - expect((iee as any).initialized).toBeFalsy(); - - iee.initialize("zowe", {logger: jest.fn() as any}); - expect((iee as any).initialized).toBeTruthy(); - - const dummyMap = { - has: () => (true), - delete: jest.fn(), - keys: () => ["dummy"], - get: () => ([{ removeAllListeners }, jest.fn()]) - }; - // Mocked map of subscriptions - (iee.instance as any).subscriptions = dummyMap; - (iee.instance as any).eventTimes = dummyMap; - - iee.teardown(); - expect(closeWatcher).toHaveBeenCalled(); - expect((iee as any).initialized).toBeFalsy(); - }); - - it("should retrieve event contents successfully", () => { - jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); - iee.initialize("zowe"); - let contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED); - expect(contents).toEqual(""); - - jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); - jest.spyOn(fs, "readFileSync").mockReturnValueOnce("dummy"); - contents = iee.instance.getEventContents(ImperativeUserEvents.ON_VAULT_CHANGED); - expect(contents).toEqual("dummy"); - }); - }); -}); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts index 17bac7fa95..671bab25e8 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/import/import.handler.unit.test.ts @@ -23,7 +23,6 @@ import { expectedConfigObject, expectedSchemaObject } from "../../../../../../__tests__/__integration__/imperative/__tests__/__integration__/cli/config/__resources__/expectedObjects"; jest.mock("fs"); -jest.mock("../../../../../events/src/ImperativeEventEmitter"); const expectedConfigText = JSONC.stringify(expectedConfigObject, null, ConfigConstants.INDENT); const expectedConfigObjectWithoutSchema = lodash.omit(expectedConfigObject, "$schema"); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts index 703e39456b..92d1af3a30 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts @@ -27,7 +27,7 @@ import { OverridesLoader } from "../../../../src/OverridesLoader"; import { ConfigUtils, ImperativeError } from "../../../../.."; jest.mock("fs"); -jest.mock("../../../../../events/src/ImperativeEventEmitter"); +// jest.mock("../../../../../events/src/ImperativeEventEmitter"); const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts index 89c4aa879c..7d1e1e7127 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { IHandlerParameters, ImperativeEventEmitter, Logger } from "../../../../.."; +import { Logger } from "../../../../../logger"; import { Config } from "../../../../../config/src/Config"; import { IConfig, IConfigOpts, IConfigProfile } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -26,8 +26,7 @@ import * as lodash from "lodash"; import * as fs from "fs"; import { SessConstants } from "../../../../../rest"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; - -jest.mock("../../../../../events/src/ImperativeEventEmitter"); +import { IHandlerParameters } from "../../../../../cmd"; let readPromptSpy: any; const getIHandlerParametersObject = (): IHandlerParameters => { @@ -103,7 +102,7 @@ describe("Configuration Secure command handler", () => { }; beforeAll( async() => { - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); + // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); diff --git a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts index cf13d6e108..f518718d72 100644 --- a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts +++ b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts @@ -10,7 +10,7 @@ */ jest.mock("../../../../logger/src/LoggerUtils"); -jest.mock("../../../../events/src/ImperativeEventEmitter"); +// jest.mock("../../../../events/src/ImperativeEventEmitter"); import * as fs from "fs"; import * as path from "path"; @@ -22,7 +22,7 @@ import { Config } from "../../../../config"; import { IConfigSecure } from "../../../../config/src/doc/IConfigSecure"; import FakeAuthHandler from "./__data__/FakeAuthHandler"; import { CredentialManagerFactory } from "../../../../security"; -import { ImperativeError, ImperativeEventEmitter } from "../../../.."; +import { ImperativeError } from "../../../.."; const MY_APP = "my_app"; @@ -38,7 +38,6 @@ describe("BaseAuthHandler config", () => { let fakeConfig: Config; beforeAll(() => { - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }}); Object.defineProperty(CredentialManagerFactory, "initialized", { get: () => true }); Object.defineProperty(ImperativeConfig, "instance", { get: () => ({ diff --git a/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts b/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts index 717be13b89..c0a045a32a 100644 --- a/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts +++ b/packages/imperative/src/rest/__tests__/session/ConnectionPropsForSessCfg.unit.test.ts @@ -10,7 +10,6 @@ */ jest.mock("../../../logger/src/LoggerUtils"); -jest.mock("../../../events/src/ImperativeEventEmitter"); import { ConnectionPropsForSessCfg } from "../../src/session/ConnectionPropsForSessCfg"; import { CliUtils } from "../../../utilities/src/CliUtils"; diff --git a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts index 0a7a6f71d1..093a13b7c0 100644 --- a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts +++ b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts @@ -17,11 +17,9 @@ import { ICredentialManagerNameMap } from "../src/doc/ICredentialManagerNameMap" import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { ImperativeEventEmitter } from "../../events"; +import { EventOperator } from "../../events"; -jest.mock("../../events/src/ImperativeEventEmitter"); - describe("CredentialManagerOverride", () => { let mockImpConfig: any; let expectedSettings: any; @@ -32,7 +30,7 @@ describe("CredentialManagerOverride", () => { cliHome: __dirname }; jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue(mockImpConfig); - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); + // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); expectedSettings = { fileName: path.join(mockImpConfig.cliHome, "settings", "imperative.json"), From e1d398cb971237e2437fbc8b391eb58fc47303df Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 10 Jun 2024 14:26:10 -0400 Subject: [PATCH 39/72] fixed test Signed-off-by: Amber Torrise --- .../events/__tests__/__unit__/EventProcessor.unit.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index 398b896c0e..5ce7035232 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -16,14 +16,10 @@ describe('EventProcessor Unit Tests', () => { }); it('constructor initializes EventProcessor correctly', () => { - // need to fix :/ expect(EventOperator['instances'].get(appName)).toBeUndefined(); - const type = IProcessorTypes.BOTH; - const processor = new EventProcessor(appName, type, logger); + EventOperator.getProcessor(appName); expect(EventOperator['instances'].get(appName)).toBeDefined(); - expect(processor.appName).toBe(appName); - expect(processor.processorType).toBe(type); }); }); From a20b7073a07a41204fc5476226cc3072f1bf4806 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:38:39 +0000 Subject: [PATCH 40/72] fix: prevent .events and extenders.json from being created, thanks @atorrise Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/CredentialManagerOverride.unit.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts index 093a13b7c0..13d1824810 100644 --- a/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts +++ b/packages/imperative/src/security/__tests__/CredentialManagerOverride.unit.test.ts @@ -17,7 +17,7 @@ import { ICredentialManagerNameMap } from "../src/doc/ICredentialManagerNameMap" import { ImperativeConfig } from "../../utilities"; import { ImperativeError } from "../../error"; import { ISettingsFile } from "../../settings/src/doc/ISettingsFile"; -import { EventOperator } from "../../events"; +import { EventOperator, EventUtils } from "../../events"; describe("CredentialManagerOverride", () => { @@ -30,7 +30,8 @@ describe("CredentialManagerOverride", () => { cliHome: __dirname }; jest.spyOn(ImperativeConfig, "instance", "get").mockReturnValue(mockImpConfig); - // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); expectedSettings = { fileName: path.join(mockImpConfig.cliHome, "settings", "imperative.json"), From 730e84388990681b330d88128df39a40e4fa8e60 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:08:45 +0000 Subject: [PATCH 41/72] fix: mocked the Event Operator on unrelated tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ProfileInfo.TeamConfig.unit.test.ts | 4 ++++ .../__unit__/EventOperator.unit.test.ts | 2 +- .../config/cmd/set/set.handler.unit.test.ts | 11 +++++---- .../npm-interface/install.unit.test.ts | 18 +++++++------- .../npm-interface/uninstall.unit.test.ts | 24 +++++++++---------- .../BaseAuthHandler.config.unit.test.ts | 11 +++++++-- .../utilities/npm-interface/install.ts | 2 +- 7 files changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index ea5a9e92c3..462935e4bd 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -34,6 +34,7 @@ import { ConfigProfiles } from "../src/api"; import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; import { Logger } from "../../logger/src/Logger"; +import { EventOperator, EventUtils } from "../../events"; const testAppNm = "ProfInfoApp"; @@ -77,6 +78,9 @@ describe("TeamConfig ProfileInfo tests", () => { process.env[testEnvPrefix + "_CLI_HOME"] = teamProjDir; // mock jsonfile.writeFileSync to avoid writing files to disk during testing writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterAll(() => { diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index f0b6d649b1..be16a8649e 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -17,7 +17,7 @@ import { Event } from '../../..'; import { EventTypes } from "../../src/EventConstants"; jest.mock('../../src/EventProcessor'); -jest.mock('../../../Logger'); +jest.mock('../../../logger'); const logger = Logger.getImperativeLogger(); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts index 08613f6777..d569a30238 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts @@ -9,7 +9,7 @@ * */ -import { IHandlerParameters, ImperativeEventEmitter } from "../../../../.."; +import { IHandlerParameters } from "../../../../../cmd"; import { Config } from "../../../../../config/src/Config"; import { IConfigOpts } from "../../../../../config"; import { ImperativeConfig } from "../../../../../utilities"; @@ -25,8 +25,9 @@ import * as path from "path"; import * as lodash from "lodash"; import * as fs from "fs"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; +import { EventOperator, EventUtils } from "../../../../../events"; -jest.mock("../../../../../events/src/ImperativeEventEmitter"); +// jest.mock("../../../../../events/src/ImperativeEventEmitter"); const getIHandlerParametersObject = (): IHandlerParameters => { @@ -98,7 +99,6 @@ describe("Configuration Set command handler", () => { }; beforeAll( async() => { - Object.defineProperty(ImperativeEventEmitter, "instance", { value: { emitEvent: jest.fn() }, configurable: true}); keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); @@ -119,6 +119,9 @@ describe("Configuration Set command handler", () => { keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterEach( () => { @@ -623,8 +626,8 @@ describe("Configuration Set command handler", () => { expect(keytarGetPasswordSpy).toHaveBeenCalledTimes(1); // No pre-existing secure values, only the combine expect(keytarSetPasswordSpy).toHaveBeenCalledTimes(1); expect(keytarSetPasswordSpy).toHaveBeenCalledWith("Zowe", "secure_config_props", fakeSecureDataExpected); - expect(writeFileSyncSpy).toHaveBeenCalledTimes(1); expect(writeFileSyncSpy).toHaveBeenNthCalledWith(1, fakeGblProjUserPath, JSON.stringify(compObj, null, 4)); // Config + expect(writeFileSyncSpy).toHaveBeenCalledTimes(1); }); it("should allow you to define an insecure property and add it to the project configuration while keeping other secure props", async () => { diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts index 6d07a1a448..a46f112ab7 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/install.unit.test.ts @@ -59,7 +59,7 @@ 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 { ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; import { updateExtendersJson } from "../../../../src/plugins/utilities/npm-interface/install"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; @@ -84,9 +84,9 @@ describe("PMF: Install Interface", () => { UpdateImpConfig_addProfiles: UpdateImpConfig.addProfiles as Mock, path: path as unknown as Mock, ConfigSchema_loadSchema: jest.spyOn(ConfigSchema, "loadSchema"), - ProfileInfo: { - readExtendersJsonFromDisk: jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk"), - writeExtendersJson: jest.spyOn(ProfileInfo, "writeExtendersJson") + ConfigUtils: { + readExtendersJson: jest.spyOn(ConfigUtils, "readExtendersJson"), + writeExtendersJson: jest.spyOn(ConfigUtils, "writeExtendersJson") } }; @@ -110,14 +110,14 @@ 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.ProfileInfo.readExtendersJsonFromDisk.mockReturnValue({ + mocks.ConfigUtils.readExtendersJson.mockReturnValue({ profileTypes: { "zosmf": { from: ["Zowe CLI"] } } }); - mocks.ProfileInfo.writeExtendersJson.mockImplementation(); + mocks.ConfigUtils.writeExtendersJson.mockImplementation(); mocks.ConfigSchema_loadSchema.mockReturnValue([mockTypeConfig]); mocks.ConfigurationLoader_load.mockReturnValue({ profiles: [mockTypeConfig] } as any); }); @@ -405,7 +405,7 @@ describe("PMF: Install Interface", () => { mocks.readFileSync.mockReturnValue(oneOldPlugin as any); if (opts.lastVersion) { - mocks.ProfileInfo.readExtendersJsonFromDisk.mockReturnValueOnce({ + mocks.ConfigUtils.readExtendersJson.mockReturnValueOnce({ profileTypes: { "test-type": { from: [oneOldPlugin.plugin1.package], @@ -426,9 +426,9 @@ describe("PMF: Install Interface", () => { if (opts.version && opts.lastVersion) { if (versionGreaterThan(opts.version, opts.lastVersion)) { - expect(mocks.ProfileInfo.writeExtendersJson).toHaveBeenCalled(); + expect(mocks.ConfigUtils.writeExtendersJson).toHaveBeenCalled(); } else { - expect(mocks.ProfileInfo.writeExtendersJson).not.toHaveBeenCalled(); + expect(mocks.ConfigUtils.writeExtendersJson).not.toHaveBeenCalled(); } } }; diff --git a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts index 1a0230e89f..368d237fa5 100644 --- a/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/plugins/utilities/npm-interface/uninstall.unit.test.ts @@ -30,7 +30,7 @@ import { PMFConstants } from "../../../../src/plugins/utilities/PMFConstants"; import { readFileSync, writeFileSync } from "jsonfile"; import { findNpmOnPath } from "../../../../src/plugins/utilities/NpmFunctions"; import { uninstall } from "../../../../src/plugins/utilities/npm-interface"; -import { ConfigSchema, ProfileInfo } from "../../../../../config"; +import { ConfigSchema, ConfigUtils } from "../../../../../config"; import mockTypeConfig from "../../__resources__/typeConfiguration"; import { ExecUtils } from "../../../../../utilities"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; @@ -248,7 +248,7 @@ describe("PMF: Uninstall Interface", () => { const blockMocks = getBlockMocks(); if (opts.schemaUpdated) { blockMocks.fs.existsSync.mockReturnValueOnce(true); - jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk").mockReturnValue({ + jest.spyOn(ConfigUtils, "readExtendersJson").mockReturnValue({ profileTypes: { "test-type": { from: ["a"], @@ -281,13 +281,13 @@ describe("PMF: Uninstall Interface", () => { describe("updateAndGetRemovedTypes", () => { const getBlockMocks = () => { - const profileInfo = { - readExtendersJsonFromDisk: jest.spyOn(ProfileInfo, "readExtendersJsonFromDisk"), - writeExtendersJson: jest.spyOn(ProfileInfo, "writeExtendersJson").mockImplementation(), + const configUtils = { + readExtendersJson: jest.spyOn(ConfigUtils, "readExtendersJson"), + writeExtendersJson: jest.spyOn(ConfigUtils, "writeExtendersJson").mockImplementation(), }; return { - profileInfo, + configUtils }; }; @@ -296,23 +296,23 @@ describe("PMF: Uninstall Interface", () => { schema?: boolean; }, extendersJson: IExtendersJsonOpts) => { const blockMocks = getBlockMocks(); - blockMocks.profileInfo.readExtendersJsonFromDisk.mockReturnValue(extendersJson); + blockMocks.configUtils.readExtendersJson.mockReturnValue(extendersJson); const hasMultipleSources = extendersJson.profileTypes["some-type"].from.length > 1; const wasLatestSource = extendersJson.profileTypes["some-type"].latestFrom === "aPluginPackage"; const typesToRemove = updateAndGetRemovedTypes("aPluginPackage"); if (shouldUpdate.extJson) { - expect(blockMocks.profileInfo.writeExtendersJson).toHaveBeenCalled(); + expect(blockMocks.configUtils.writeExtendersJson).toHaveBeenCalled(); } else { - expect(blockMocks.profileInfo.writeExtendersJson).not.toHaveBeenCalled(); + expect(blockMocks.configUtils.writeExtendersJson).not.toHaveBeenCalled(); return; } - const newExtendersObj = blockMocks.profileInfo.writeExtendersJson.mock.calls[0][0]; + const newExtendersObj = blockMocks.configUtils.writeExtendersJson.mock.calls[0][0]; if (hasMultipleSources) { - expect(blockMocks.profileInfo.writeExtendersJson).not.toHaveBeenCalledWith( + expect(blockMocks.configUtils.writeExtendersJson).not.toHaveBeenCalledWith( expect.objectContaining({ profileTypes: { "some-type": { @@ -369,7 +369,7 @@ describe("PMF: Uninstall Interface", () => { it("returns an empty list when package does not contribute any profile types", () => { const blockMocks = getBlockMocks(); - blockMocks.profileInfo.readExtendersJsonFromDisk.mockReturnValue({ + blockMocks.configUtils.readExtendersJson.mockReturnValue({ profileTypes: { "some-type": { from: ["anotherPkg"] diff --git a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts index f518718d72..12b0696866 100644 --- a/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts +++ b/packages/imperative/src/imperative/src/auth/__tests__/BaseAuthHandler.config.unit.test.ts @@ -10,7 +10,6 @@ */ jest.mock("../../../../logger/src/LoggerUtils"); -// jest.mock("../../../../events/src/ImperativeEventEmitter"); import * as fs from "fs"; import * as path from "path"; @@ -22,7 +21,7 @@ import { Config } from "../../../../config"; import { IConfigSecure } from "../../../../config/src/doc/IConfigSecure"; import FakeAuthHandler from "./__data__/FakeAuthHandler"; import { CredentialManagerFactory } from "../../../../security"; -import { ImperativeError } from "../../../.."; +import { EventOperator, EventUtils, ImperativeError } from "../../../.."; const MY_APP = "my_app"; @@ -47,6 +46,11 @@ describe("BaseAuthHandler config", () => { }); }); + beforeEach(() => { + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); + }); + afterEach(() => { jest.restoreAllMocks(); jest.clearAllMocks(); @@ -465,6 +469,9 @@ describe("BaseAuthHandler config", () => { jest.spyOn(Config, "search").mockReturnValue(configPath); jest.spyOn(fs, "existsSync").mockReturnValueOnce(false).mockReturnValueOnce(true).mockReturnValue(false); fakeConfig = await Config.load(MY_APP, { vault: fakeVault }); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); it("should logout successfully from profile specified by user", async () => { diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts index d8c02e7483..3e23e0f890 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts @@ -212,7 +212,7 @@ export async function install(packageLocation: string, registry: string, install if (shouldUpdate) { // Update extenders.json (if necessary) after installing the plugin - ProfileInfo.writeExtendersJson(extendersJson); + ConfigUtils.writeExtendersJson(extendersJson); } const schema = ConfigSchema.buildSchema(loadedSchema); ConfigSchema.updateSchema({ layer: "global", schema }); From 06a64576a45c4b3b6626262947c988165e7a34a1 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:40:42 +0000 Subject: [PATCH 42/72] fix: a few more unit tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ProfileInfo.TeamConfig.unit.test.ts | 31 +++++++++++-------- .../cmd/secure/secure.handler.unit.test.ts | 4 +++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index 462935e4bd..ae5c348ba1 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -40,6 +40,8 @@ import { EventOperator, EventUtils } from "../../events"; const testAppNm = "ProfInfoApp"; const testEnvPrefix = testAppNm.toUpperCase(); const profileTypes = ["zosmf", "tso", "base", "dummy"]; +const testDir = path.join(__dirname, "__resources__"); +const teamProjDir = path.join(testDir, testAppNm + "_team_config_proj"); function createNewProfInfo(newDir: string, opts?: IProfOpts): ProfileInfo { // create a new ProfileInfo in the desired directory @@ -54,8 +56,6 @@ describe("TeamConfig ProfileInfo tests", () => { const tsoName = "tsoProfName"; const tsoProfName = "LPAR1.tsoProfName"; const tsoJsonLoc = "profiles.LPAR1.profiles." + tsoName; - const testDir = path.join(__dirname, "__resources__"); - const teamProjDir = path.join(testDir, testAppNm + "_team_config_proj"); const userTeamProjDir = path.join(testDir, testAppNm + "_user_and_team_config_proj"); const teamHomeProjDir = path.join(testDir, testAppNm + "_home_team_config_proj"); const largeTeamProjDir = path.join(testDir, testAppNm + "_large_team_config_proj"); @@ -78,9 +78,6 @@ describe("TeamConfig ProfileInfo tests", () => { process.env[testEnvPrefix + "_CLI_HOME"] = teamProjDir; // mock jsonfile.writeFileSync to avoid writing files to disk during testing writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); - - jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); - jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterAll(() => { @@ -894,8 +891,14 @@ describe("TeamConfig ProfileInfo tests", () => { it("should not look for secure properties in the global-layer base profile if it does not exist", async () => { process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; const profInfo = createNewProfInfo(userTeamProjDir); + process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; await profInfo.readProfilesFromDisk(); const profiles = profInfo.getAllProfiles(); + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + + // TODO(zFernand0): investigate why global layer profiles are not loaded + expect(profiles).toEqual([]); // This should prove the above statement + const desiredProfile = "TEST001.first"; const profAttrs = profiles.find(p => p.profName === desiredProfile); let mergedArgs; @@ -1188,7 +1191,7 @@ describe("TeamConfig ProfileInfo tests", () => { const storageSpy = jest.spyOn(ConfigAutoStore as any, "_storeSessCfgProps").mockResolvedValue(undefined); const profiles = [{ type: "test", schema: {} as any }]; ImperativeConfig.instance.loadedConfig.profiles = profiles; - ImperativeConfig.instance.loadedConfig.baseProfile = null; + ImperativeConfig.instance.loadedConfig.baseProfile = undefined; let caughtError; try { @@ -1318,11 +1321,11 @@ describe("TeamConfig ProfileInfo tests", () => { const mergedArgs = profInfo.mergeArgsForProfile(profAttrs); const userArg = mergedArgs.knownArgs.find((arg) => arg.argName === "user"); - expect(userArg.argValue).toBe("userNameBase"); + expect(userArg?.argValue).toBe("userNameBase"); expect(profInfo.loadSecureArg(userArg as IProfArgAttrs)).toBe("userNameBase"); const passwordArg = mergedArgs.knownArgs.find((arg) => arg.argName === "password"); - expect(passwordArg.argValue).toBe("passwordBase"); + expect(passwordArg?.argValue).toBe("passwordBase"); expect(profInfo.loadSecureArg(passwordArg as IProfArgAttrs)).toBe("passwordBase"); }); @@ -1333,10 +1336,10 @@ describe("TeamConfig ProfileInfo tests", () => { const mergedArgs = profInfo.mergeArgsForProfile(profAttrs, { getSecureVals: true }); const userArg = mergedArgs.knownArgs.find((arg) => arg.argName === "user"); - expect(userArg.argValue).toBe("userNameBase"); + expect(userArg?.argValue).toBe("userNameBase"); const passwordArg = mergedArgs.knownArgs.find((arg) => arg.argName === "password"); - expect(passwordArg.argValue).toBe("passwordBase"); + expect(passwordArg?.argValue).toBe("passwordBase"); }); it("should treat secure arg as plain text if loaded from environment variable", async () => { @@ -1363,7 +1366,7 @@ describe("TeamConfig ProfileInfo tests", () => { profInfo.loadSecureArg({ argName: "test", dataType: "string", - argValue: undefined, + argValue: undefined as any, argLoc: { locType: ProfLocType.DEFAULT } }); } catch (error) { @@ -1390,8 +1393,10 @@ describe("TeamConfig ProfileInfo tests", () => { const profAttrs = profInfo.getDefaultProfile("zosmf") as IProfAttrs; const osLocInfo = profInfo.getOsLocInfo(profAttrs); const expectedObjs = [ - { name: profAttrs.profName, path: profAttrs.profLoc.osLoc[0], user: false, global: false }, - { name: profAttrs.profName, path: profAttrs.profLoc.osLoc[0], user: false, global: true } + { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: false }, + // TODO(zFernand0): Investigate why only the team project is present in the osLoc array + // Possible reason: global layer not loaded by getAllProfiles() + // { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: true } ]; expect(osLocInfo).toBeDefined(); expect(osLocInfo.length).toBe(expectedObjs.length); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts index 7d1e1e7127..9e0a4338fd 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts @@ -27,6 +27,7 @@ import * as fs from "fs"; import { SessConstants } from "../../../../../rest"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; import { IHandlerParameters } from "../../../../../cmd"; +import { EventOperator, EventUtils } from "../../../../../events"; let readPromptSpy: any; const getIHandlerParametersObject = (): IHandlerParameters => { @@ -124,6 +125,9 @@ describe("Configuration Secure command handler", () => { keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); readPromptSpy.mockClear(); + + jest.spyOn(EventUtils, "validateAppName").mockImplementation(jest.fn()); + jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); afterEach( () => { From 5fac200b884570bae750817e9cb9276b540ebead Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 11 Jun 2024 08:26:20 -0400 Subject: [PATCH 43/72] just 1 failing unit test for eventProcessor Signed-off-by: Amber Torrise --- .../__unit__/EventProcessor.unit.test.ts | 145 ++++++++++++++++-- .../.events/Zowe/onCredentialManagerChanged | 7 + 2 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index 5ce7035232..9df5077c22 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -1,25 +1,152 @@ import { EventProcessor } from '../../src/EventProcessor'; -import { Logger } from '../../../logger/src/Logger'; +import { EventUtils } from '../../src/EventUtils'; import { IProcessorTypes } from '../../src/doc/IEventInstanceTypes'; -import { EventOperator } from '../..'; +import { ImperativeError } from '../../../error/src/ImperativeError'; +import { Event } from '../../src/Event'; +import { EventTypes } from '../../src/EventConstants'; +import { EventOperator } from '../../src/EventOperator'; jest.mock('../../../logger/src/Logger'); jest.mock('../../src/EventUtils'); jest.mock('../../../error/src/ImperativeError'); describe('EventProcessor Unit Tests', () => { - const appName = 'TestApp'; - const logger = Logger.getImperativeLogger(); + const isSharedEventSpy = jest.spyOn(EventUtils, 'isSharedEvent'); + const isUserEventSpy = jest.spyOn(EventUtils, 'isUserEvent'); + const writeEventSpy = jest.spyOn(EventUtils, 'writeEvent'); + const createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription'); beforeEach(() => { jest.clearAllMocks(); }); - it('constructor initializes EventProcessor correctly', () => { - expect(EventOperator['instances'].get(appName)).toBeUndefined(); + describe('Constructor', () => { + it('initializes EventProcessor correctly', () => { + const appName = 'someApp'; - EventOperator.getProcessor(appName); + expect(EventOperator['instances'].get(appName)).toBeUndefined(); - expect(EventOperator['instances'].get(appName)).toBeDefined(); + EventOperator.getProcessor(appName); + + expect(EventOperator['instances'].get(appName)).toBeDefined(); + }); + }); + + describe('Subscription Methods', () => { + it('subscribeShared throws error for emitter-only processor', () => { + const appName = 'toEmit'; + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + + expect(() => emitter.subscribeShared('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); + }); + + it('subscribeUser throws error for emitter-only processor', () => { + const appName = 'toEmit'; + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + + expect(() => emitter.subscribeUser('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); + }); + + it('subscribeShared correctly subscribes to shared events', () => { + const appName = 'toSubscribeAndEmit'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'someEvent'; + const callbacks = [jest.fn()]; + + isSharedEventSpy.mockReturnValue(true); + createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); + + const disposable = processor.subscribeShared(eventName, callbacks); + + expect(EventUtils.createSubscription).toHaveBeenCalledWith(processor, eventName, EventTypes.ZoweSharedEvents); + expect(EventUtils.setupWatcher).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(disposable).toBeDefined(); + }); + + it('subscribeUser correctly subscribes to user-specific events', () => { + const appName = 'toSubscribeAndEmit'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'someEvent'; + const callbacks = [jest.fn()]; + + isUserEventSpy.mockReturnValue(true); + createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); + + const disposable = processor.subscribeUser(eventName, callbacks); + + expect(EventUtils.createSubscription).toHaveBeenCalledWith(processor, eventName, EventTypes.ZoweUserEvents); + expect(EventUtils.setupWatcher).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(disposable).toBeDefined(); + }); + }); + + describe('Emission Methods', () => { + it('emitEvent throws error for watcher-only processor', () => { + const appName = 'toSubscribeTo'; + const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); + + expect(() => watcher.emitEvent('someEvent')).toThrow(ImperativeError); + }); + + it('emitEvent updates event timestamp and writes event', () => { + const appName = 'toEmit'; + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + const eventName = 'someEvent'; + const event = { eventTime: '', eventName, eventType: EventTypes.UserEvents, appName, subscriptions: new Set() } as unknown as Event; + + emitter.subscribedEvents.set(eventName, event); + isSharedEventSpy.mockReturnValue(false); + isUserEventSpy.mockReturnValue(false); + writeEventSpy.mockImplementation(() => {}); + + emitter.emitEvent(eventName); + + expect(event.eventTime).not.toBe(''); + expect(EventUtils.writeEvent).toHaveBeenCalledWith(event); + }); + + it('emitZoweEvent updates event timestamp and writes event', () => { + const appName = 'toEmit'; + const emitter = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'ZoweEvent'; + const event = { eventTime: '', eventName, eventType: EventTypes.ZoweSharedEvents, appName, subscriptions: new Set() } as unknown as Event; + emitter.subscribedEvents.set(eventName, event); + + writeEventSpy.mockImplementation(() => {}); + + emitter.emitZoweEvent(eventName); + + expect(event.eventTime).not.toBe(''); + expect(EventUtils.writeEvent).toHaveBeenCalledWith(event); + }); + }); + + describe('Unsubscribe Methods', () => { + it('unsubscribe removes subscriptions and cleans up resources', () => { + const appName = 'toSubscribeAndEmit'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const eventName = 'someEvent'; + const subscription = { close: jest.fn() }; + const event = { + eventTime: '', + eventName, eventType: EventTypes.ZoweSharedEvents, + appName, + subscriptions: subscription + } as unknown as Event; + processor.subscribedEvents.set(eventName, event); + + processor.unsubscribe(eventName); + + expect(subscription.close).toHaveBeenCalled(); + expect(processor.subscribedEvents.has(eventName)).toBe(false); + }); + + it('unsubscribe throws error for emitter-only processor', () => { + const appName = 'toEmit'; + + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + + expect(() => emitter.unsubscribe('someEvent')).toThrow(ImperativeError); + }); }); -}); +}); \ No newline at end of file diff --git a/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged b/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged new file mode 100644 index 0000000000..801a37dc93 --- /dev/null +++ b/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged @@ -0,0 +1,7 @@ +{ + "eventTime": "2024-06-10T18:29:35.048Z", + "eventName": "onCredentialManagerChanged", + "eventType": null, + "appName": "Zowe", + "eventFilePath": "C:\\Users\\at895452\\Desktop\\zowe-cli-v3\\zowe-cli\\packages\\imperative\\src\\security\\__tests__\\.events\\Zowe\\onCredentialManagerChanged" +} \ No newline at end of file From c799e786ea272e642dfdff1c286f8441bd541b20 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Tue, 11 Jun 2024 11:19:25 -0400 Subject: [PATCH 44/72] finishing all unit tests and also editing EventProcessor.unsubscribe Signed-off-by: Amber Torrise --- .../__unit__/EventOperator.unit.test.ts | 14 ++- .../__unit__/EventProcessor.unit.test.ts | 98 ++++++++++++------- .../src/events/src/EventProcessor.ts | 40 +++++--- 3 files changed, 102 insertions(+), 50 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index be16a8649e..f9633681ee 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -14,7 +14,7 @@ import { EventProcessor } from '../../src/EventProcessor'; import { Logger } from '../../..'; import { IProcessorTypes } from '../../src/doc'; import { Event } from '../../..'; -import { EventTypes } from "../../src/EventConstants"; +import { EventTypes, ZoweUserEvents } from "../../src/EventConstants"; jest.mock('../../src/EventProcessor'); jest.mock('../../../logger'); @@ -39,9 +39,21 @@ describe("EventOperator Unit Tests", () => { it("'getZoweProcessor' should return the Zowe processor instance", () => { const processor = EventOperator.getZoweProcessor(); + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.BOTH, logger); expect(processor).toBeInstanceOf(EventProcessor); }); + it('emitZoweEvent is called by a Zowe processor and emits a ZoweUserEvents', () => { + const processor = EventOperator.getZoweProcessor(); + const eventName = "onVaultChanged"; + const emitZoweEventSpy = jest.spyOn(processor, 'emitZoweEvent'); + + processor.emitZoweEvent(eventName); + + expect(emitZoweEventSpy).toHaveBeenCalledWith(eventName); + expect(Object.values(ZoweUserEvents)).toContain(eventName); + }); + it("'getProcessor' should return a generic event processor", () => { const appName = 'GenericApp'; const processor = EventOperator.getProcessor(appName, logger); diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index 9df5077c22..473f931a36 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -1,3 +1,14 @@ +/* +* 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 { EventProcessor } from '../../src/EventProcessor'; import { EventUtils } from '../../src/EventUtils'; import { IProcessorTypes } from '../../src/doc/IEventInstanceTypes'; @@ -33,21 +44,21 @@ describe('EventProcessor Unit Tests', () => { }); describe('Subscription Methods', () => { - it('subscribeShared throws error for emitter-only processor', () => { + it('"subscribeShared" throws error for emitter-only processor', () => { const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); expect(() => emitter.subscribeShared('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); }); - it('subscribeUser throws error for emitter-only processor', () => { + it('"subscribeUser" throws error for emitter-only processor', () => { const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); expect(() => emitter.subscribeUser('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); }); - it('subscribeShared correctly subscribes to shared events', () => { + it('"subscribeShared" correctly subscribes to shared events', () => { const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); const eventName = 'someEvent'; @@ -63,7 +74,7 @@ describe('EventProcessor Unit Tests', () => { expect(disposable).toBeDefined(); }); - it('subscribeUser correctly subscribes to user-specific events', () => { + it('"subscribeUser" correctly subscribes to user-specific events', () => { const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); const eventName = 'someEvent'; @@ -81,14 +92,21 @@ describe('EventProcessor Unit Tests', () => { }); describe('Emission Methods', () => { - it('emitEvent throws error for watcher-only processor', () => { + it('"emitEvent" throws error for watcher-only processor', () => { const appName = 'toSubscribeTo'; const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); expect(() => watcher.emitEvent('someEvent')).toThrow(ImperativeError); }); - it('emitEvent updates event timestamp and writes event', () => { + it('"emitZoweEvent" throws error for watcher-only processor', () => { + const appName = 'toSubscribeTo'; + const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); + + expect(() => watcher.emitZoweEvent('someEvent')).toThrow(ImperativeError); + }); + + it('"emitEvent" updates event timestamp and writes event', () => { const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); const eventName = 'someEvent'; @@ -104,44 +122,58 @@ describe('EventProcessor Unit Tests', () => { expect(event.eventTime).not.toBe(''); expect(EventUtils.writeEvent).toHaveBeenCalledWith(event); }); - - it('emitZoweEvent updates event timestamp and writes event', () => { - const appName = 'toEmit'; - const emitter = new EventProcessor(appName, IProcessorTypes.BOTH); - const eventName = 'ZoweEvent'; - const event = { eventTime: '', eventName, eventType: EventTypes.ZoweSharedEvents, appName, subscriptions: new Set() } as unknown as Event; - emitter.subscribedEvents.set(eventName, event); - - writeEventSpy.mockImplementation(() => {}); - - emitter.emitZoweEvent(eventName); - - expect(event.eventTime).not.toBe(''); - expect(EventUtils.writeEvent).toHaveBeenCalledWith(event); - }); }); describe('Unsubscribe Methods', () => { - it('unsubscribe removes subscriptions and cleans up resources', () => { + it('"unsubscribe" removes subscriptions and cleans up resources', () => { const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); - const eventName = 'someEvent'; - const subscription = { close: jest.fn() }; + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + processor.unsubscribe('testEvent'); + + expect(processor.subscribedEvents.has('testEvent')).toBe(false); + }); + it('subscription removed from a processor\'s subscribed events and resources are cleaned', () => { + const appName = 'toSubscribeAndEmit'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + const mockSubscription = { + removeAllListeners: jest.fn(), + close: jest.fn() + }; + const event = { eventTime: '', - eventName, eventType: EventTypes.ZoweSharedEvents, - appName, - subscriptions: subscription + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([mockSubscription]) } as unknown as Event; - processor.subscribedEvents.set(eventName, event); - processor.unsubscribe(eventName); + processor.subscribedEvents = new Map([ + ['testEvent', event] + ]); - expect(subscription.close).toHaveBeenCalled(); - expect(processor.subscribedEvents.has(eventName)).toBe(false); + expect(processor.subscribedEvents.has('testEvent')).toBe(true); + processor.unsubscribe('testEvent'); + expect(mockSubscription.removeAllListeners).toHaveBeenCalled(); + expect(mockSubscription.close).toHaveBeenCalled(); + expect(processor.subscribedEvents.has('testEvent')).toBe(false); }); - - it('unsubscribe throws error for emitter-only processor', () => { + it('"unsubscribe" throws error for emitter-only processor', () => { const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 07d6dfc1fe..9ea2853743 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -10,7 +10,7 @@ */ import { Logger } from "../../logger/src/Logger"; -import { EventCallback, EventTypes } from "./EventConstants"; +import { EventCallback, EventTypes, ZoweSharedEvents, ZoweUserEvents } from "./EventConstants"; import { ImperativeError } from "../../error/src/ImperativeError"; import { Event } from "./Event"; import { ConfigUtils } from "../../config/src/ConfigUtils"; @@ -122,16 +122,23 @@ export class EventProcessor { * @throws {ImperativeError} - If the event cannot be emitted. */ public emitZoweEvent(eventName: string): void { - if (this.processorType === IProcessorTypes.WATCHER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); - } - try { - const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); - event.eventTime = new Date().toISOString(); - EventUtils.writeEvent(event); - } catch (err) { - throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); + if (this.appName === "Zowe") { + if (this.processorType === IProcessorTypes.WATCHER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); + } + if (!Object.values(ZoweUserEvents).includes(eventName as ZoweUserEvents) && + !Object.values(ZoweSharedEvents).includes(eventName as ZoweSharedEvents)) { + throw new ImperativeError({ msg: `Invalid Zowe event: ${eventName}` }); + } + try { + const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); + event.eventTime = new Date().toISOString(); + EventUtils.writeEvent(event); + } catch (err) { + throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); + } } + throw new ImperativeError({ msg: `Processor does not have Zowe permissions: ${eventName}` }); } /** @@ -144,14 +151,15 @@ export class EventProcessor { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); } - try { - // find watcher list and close everything - this.subscribedEvents.get(eventName).subscriptions.forEach((watcher) => { - watcher.removeAllListeners(eventName).close(); + const event = this.subscribedEvents.get(eventName); + if (event) { + event.subscriptions.forEach(subscription => { + subscription.removeAllListeners(eventName); + if (typeof subscription.close === 'function') { + subscription.close(); + } }); this.subscribedEvents.delete(eventName); - } catch (err) { - throw new ImperativeError({ msg: `Error unsubscribing from event: ${eventName}`, causeErrors: err }); } } } \ No newline at end of file From a826e933ffd9dc30416715fcc15f79c8ba60d940 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 13 Jun 2024 13:44:49 -0400 Subject: [PATCH 45/72] integration tests Signed-off-by: Amber Torrise --- .../EventEmitter.integration.test.ts | 97 ------------------- ...Operator_and_Processor.integration.test.ts | 83 ++++++++++++++++ .../src/events/src/EventProcessor.ts | 11 +-- 3 files changed, 87 insertions(+), 104 deletions(-) delete mode 100644 packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts create mode 100644 packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts diff --git a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts deleted file mode 100644 index fb2718e3a5..0000000000 --- a/packages/imperative/src/events/__tests__/__integration__/EventEmitter.integration.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* -* 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 { EventOperator, EventTypes, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; -import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; -import { TestLogger } from "../../../../__tests__/src/TestLogger"; -import * as TestUtil from "../../../../__tests__/src/TestUtil"; -import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; -import * as fs from "fs"; -import * as path from "path"; - -let TEST_ENVIRONMENT: ITestEnvironment; -let cwd = ''; -const appName = "Zowe"; - -describe("Event Emitter", () => { - const mainModule = process.mainModule; - const testLogger = TestLogger.getTestLogger(); - - beforeAll(async () => { - (process.mainModule as any) = { - filename: __filename - }; - - TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ - cliHomeEnvVar: "ZOWE_CLI_HOME", - testName: "event_emitter" - }); - cwd = TEST_ENVIRONMENT.workingDir; - }); - - afterAll(() => { - process.mainModule = mainModule; - TestUtil.rimraf(cwd); - }); - - const doesEventFileExists = (eventName: string) => { - const eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : - EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.SharedEvents; - - const eventDir = EventUtils.getEventDir(appName); - if (!fs.existsSync(eventDir)) return false; - if (fs.existsSync(path.join(eventDir, appName, eventName))) return true; - return false; - }; - - describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const theProc = EventOperator.getZoweProcessor() - - expect(doesEventFileExists(theEvent)).toBeFalsy(); - expect((theProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); - - const subSpy = jest.fn(); - theProc.subscribeShared(theEvent, subSpy); - - expect(subSpy).not.toHaveBeenCalled(); - expect(doesEventFileExists(theEvent)).toBeTruthy(); - - theProc.emitEvent(theEvent); - - (theProc as any).subscribedEvents.get(theEvent).subscriptions[0](); // simulate FSWatcher called - - expect(doesEventFileExists(theEvent)).toBeTruthy(); - const eventDetails: IEventJson = (theProc as any).subscribedEvents.get(theEvent).toJson(); - expect(eventDetails.eventName).toEqual(theEvent); - expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); - - expect(subSpy).toHaveBeenCalled(); - - EventOperator.deleteProcessor(appName); - }); - it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); - - describe("User Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); - it("should trigger subscriptions for all instances watching for onVaultChanged", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); - - describe("Custom Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { }); - it("should trigger subscriptions for all instances watching for onMyCustomEvent", () => { }); - it("should not affect subscriptions from another instance when unsubscribing from events", () => { }); - }); -}); diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts new file mode 100644 index 0000000000..fe29c89b5d --- /dev/null +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -0,0 +1,83 @@ +/* +* 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 { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import * as fs from "fs"; +import * as path from "path"; +import * as TestUtil from "../../../../__tests__/src/TestUtil"; +import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; +import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; + +let TEST_ENVIRONMENT: ITestEnvironment; +const appName = "Zowe"; +const userHome = require('os').homedir(); +const zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + +describe("Event Operator and Processor", () => { + + beforeAll(async () => { + TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_operator_and_processor" + }); + }); + + const cleanupDirectories = () => { + const userEventsDir = path.join(userHome, '.zowe', '.events'); + if (fs.existsSync(userEventsDir)) { + fs.rmdirSync(userEventsDir, { recursive: true }); + } + + const sharedEventsDir = path.join(zoweCliHome, '.events'); + if (fs.existsSync(sharedEventsDir)) { + fs.rmdirSync(sharedEventsDir, { recursive: true }); + } + }; + + afterEach(cleanupDirectories); + + afterAll(() => { + TestUtil.rimraf(userHome); + TestUtil.rimraf(zoweCliHome); + }); + + const doesEventFileExist = (eventDir: string, eventName: string) => { + const eventFilePath = path.join(eventDir, eventName); + return fs.existsSync(eventFilePath); + }; + + describe("Shared Events", () => { + it("should create an event file upon first subscription if the file does not exist", () => { + const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const theWatcher = EventOperator.getWatcher("sample"); + const theEmitter = EventOperator.getZoweProcessor(); + + const eventDir = path.join(zoweCliHome, '.events'); + expect(doesEventFileExist(eventDir, theEvent)).toBeFalsy(); + expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + + const theCallback = jest.fn() as EventCallback; + theWatcher.subscribeShared(theEvent, theCallback); + + expect(theCallback).not.toHaveBeenCalled(); + expect(doesEventFileExist(eventDir, theEvent)).toBeTruthy(); + + theEmitter.emitZoweEvent(theEvent); + + const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); + expect(eventDetails.eventName).toEqual(theEvent); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(theCallback).toHaveBeenCalled(); + + EventOperator.deleteProcessor(appName); + }); + }); +}); \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 9ea2853743..c468a7ddc8 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -10,7 +10,7 @@ */ import { Logger } from "../../logger/src/Logger"; -import { EventCallback, EventTypes, ZoweSharedEvents, ZoweUserEvents } from "./EventConstants"; +import { EventCallback, EventTypes } from "./EventConstants"; import { ImperativeError } from "../../error/src/ImperativeError"; import { Event } from "./Event"; import { ConfigUtils } from "../../config/src/ConfigUtils"; @@ -123,11 +123,7 @@ export class EventProcessor { */ public emitZoweEvent(eventName: string): void { if (this.appName === "Zowe") { - if (this.processorType === IProcessorTypes.WATCHER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); - } - if (!Object.values(ZoweUserEvents).includes(eventName as ZoweUserEvents) && - !Object.values(ZoweSharedEvents).includes(eventName as ZoweSharedEvents)) { + if (!EventUtils.isUserEvent(eventName) && !EventUtils.isSharedEvent(eventName)) { throw new ImperativeError({ msg: `Invalid Zowe event: ${eventName}` }); } try { @@ -137,8 +133,9 @@ export class EventProcessor { } catch (err) { throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); } + }else{ + throw new ImperativeError({ msg: `Processor does not have Zowe permissions: ${eventName}` }); } - throw new ImperativeError({ msg: `Processor does not have Zowe permissions: ${eventName}` }); } /** From 4aadd3b61961514ee561f0525f0f101a7f2594b5 Mon Sep 17 00:00:00 2001 From: "Andrew W. Harn" Date: Fri, 14 Jun 2024 11:43:40 -0400 Subject: [PATCH 46/72] Make some FS changes and use more restrictive file modes. Signed-off-by: Andrew W. Harn --- packages/imperative/src/events/src/EventUtils.ts | 6 +++--- .../src/imperative/src/config/cmd/init/init.handler.ts | 2 -- .../src/plugins/utilities/npm-interface/install.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index a62ad60e66..bcd89dbf95 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -17,7 +17,6 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { IEventDisposable, IEventJson } from "./doc"; import { Event } from "./Event"; import { EventProcessor } from "./EventProcessor"; -import { IO } from "../../io"; /** * A collection of helper functions related to event processing, including: @@ -129,7 +128,7 @@ export class EventUtils { public static ensureEventsDirExists(directoryPath: string) { try { if (!fs.existsSync(directoryPath)) { - IO.mkdirp(directoryPath); + fs.mkdirSync(directoryPath, {mode: 0o750, recursive: true}); // user read/write/exec, group read/exec } } catch (err) { throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); @@ -144,7 +143,8 @@ export class EventUtils { public static ensureFileExists(filePath: string) { try { if (!fs.existsSync(filePath)) { - fs.closeSync(fs.openSync(filePath, 'w')); + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + fs.closeSync(fs.openSync(filePath, 'w+', 0o640)); // user read/write, group read } } catch (err) { throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); diff --git a/packages/imperative/src/imperative/src/config/cmd/init/init.handler.ts b/packages/imperative/src/imperative/src/config/cmd/init/init.handler.ts index 6217b2cf70..f31a379de0 100644 --- a/packages/imperative/src/imperative/src/config/cmd/init/init.handler.ts +++ b/packages/imperative/src/imperative/src/config/cmd/init/init.handler.ts @@ -105,11 +105,9 @@ export default class InitHandler implements ICommandHandler { if (params.arguments.prompt !== false && config.api.secure.loadFailed && config.api.secure.secureFields().length > 0) { const warning = ConfigUtils.secureSaveError(); let message = "Warning:\n" + warning.message + " Skipped prompting for credentials."; - if (warning.additionalDetails) { message += `\n\n${warning.additionalDetails}\n`; } - params.response.console.log(TextUtils.chalk.yellow(message)); } diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts index 3e23e0f890..164af13fa6 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/install.ts @@ -26,7 +26,7 @@ import { UpdateImpConfig } from "../../../UpdateImpConfig"; import { CredentialManagerOverride, ICredentialManagerNameMap } from "../../../../../security"; import { IProfileTypeConfiguration } from "../../../../../profiles"; import * as semver from "semver"; -import { ConfigUtils, ProfileInfo } from "../../../../../config"; +import { ConfigUtils } from "../../../../../config"; import { IExtendersJsonOpts } from "../../../../../config/src/doc/IExtenderOpts"; // Helper function to update extenders.json object during plugin install. From 3d111bac2c0e1a345afc4e6170242520682c2317 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Fri, 14 Jun 2024 19:31:53 +0000 Subject: [PATCH 47/72] tests: fix some unit tests and add more coverage Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../ProfileInfo.TeamConfig.unit.test.ts | 2 +- .../__unit__/EventOperator.unit.test.ts | 27 ++-- .../__unit__/EventProcessor.unit.test.ts | 65 ++++---- .../__unit__/EventUtils.unit.test.ts | 153 ++++++++++++++++++ .../src/events/src/EventOperator.ts | 1 - .../src/events/src/EventProcessor.ts | 40 ++--- .../imperative/src/events/src/EventUtils.ts | 21 +-- 7 files changed, 224 insertions(+), 85 deletions(-) create mode 100644 packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index ae5c348ba1..3f1702c9b3 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -894,7 +894,7 @@ describe("TeamConfig ProfileInfo tests", () => { process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; await profInfo.readProfilesFromDisk(); const profiles = profInfo.getAllProfiles(); - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + // expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); // TODO(zFernand0): investigate why global layer profiles are not loaded expect(profiles).toEqual([]); // This should prove the above statement diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index f9633681ee..8c86fc1029 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -11,24 +11,33 @@ import { EventOperator } from '../../src/EventOperator'; import { EventProcessor } from '../../src/EventProcessor'; -import { Logger } from '../../..'; +import { EventUtils, Logger } from '../../..'; import { IProcessorTypes } from '../../src/doc'; import { Event } from '../../..'; import { EventTypes, ZoweUserEvents } from "../../src/EventConstants"; -jest.mock('../../src/EventProcessor'); jest.mock('../../../logger'); +jest.mock('../../src/EventProcessor'); +jest.mock('../../src/Event'); const logger = Logger.getImperativeLogger(); +const appName = 'TestApp'; describe("EventOperator Unit Tests", () => { beforeEach(() => { jest.clearAllMocks(); + jest.spyOn(EventUtils, "getListOfApps").mockReturnValue(["Zowe", appName]); + const subs = EventProcessor.prototype.subscribedEvents = new Map(); + const dummyEvent: any = { subscriptions: [ { removeAllListeners: jest.fn().mockReturnValue({close: jest.fn()})} as any] } ; + subs.set("Zowe", dummyEvent); + }); + afterEach(() => { + EventOperator.deleteProcessor("Zowe"); + EventOperator.deleteProcessor(appName); }); describe("processor tests", () => { it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { - const appName = 'TestApp'; const type = IProcessorTypes.BOTH; const processor = EventOperator.getProcessor(appName, logger); @@ -55,7 +64,6 @@ describe("EventOperator Unit Tests", () => { }); it("'getProcessor' should return a generic event processor", () => { - const appName = 'GenericApp'; const processor = EventOperator.getProcessor(appName, logger); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); @@ -63,7 +71,6 @@ describe("EventOperator Unit Tests", () => { }); it("'deleteProcessor' should remove the correct event processor", () => { - const appName = 'DeleteApp'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); processor.subscribedEvents = new Map([ ['testEvent', { @@ -87,15 +94,19 @@ describe("EventOperator Unit Tests", () => { }); describe("watcher tests", () => { + it("'getWatcher' should return a Zowe watcher as the default", () => { + const processor = EventOperator.getWatcher(); + + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.WATCHER, undefined); + expect(processor).toBeInstanceOf(EventProcessor); + }); it("'getWatcher' should return a watcher-only event processor", () => { - const appName = 'WatcherApp'; const processor = EventOperator.getWatcher(appName, logger); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); expect(processor).toBeInstanceOf(EventProcessor); }); it("'deleteWatcher' should remove the correct event processor", () => { - const appName = 'DeleteWatcher'; const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); processor.subscribedEvents = new Map([ ['testEvent', { @@ -120,7 +131,6 @@ describe("EventOperator Unit Tests", () => { describe("emitter tests", () => { it("'getEmitter' should return an emitter-only event processor", () => { - const appName = 'EmitterApp'; const processor = EventOperator.getEmitter(appName, logger); expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); @@ -128,7 +138,6 @@ describe("EventOperator Unit Tests", () => { }); it("'deleteEmitter' should remove the correct event processor", () => { - const appName = 'DeleteEmitter'; const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); processor.subscribedEvents = new Map([ ['testEvent', { diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index 473f931a36..9f24d4abcd 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -14,27 +14,33 @@ import { EventUtils } from '../../src/EventUtils'; import { IProcessorTypes } from '../../src/doc/IEventInstanceTypes'; import { ImperativeError } from '../../../error/src/ImperativeError'; import { Event } from '../../src/Event'; -import { EventTypes } from '../../src/EventConstants'; +import { EventTypes, ZoweSharedEvents, ZoweUserEvents } from '../../src/EventConstants'; import { EventOperator } from '../../src/EventOperator'; jest.mock('../../../logger/src/Logger'); -jest.mock('../../src/EventUtils'); -jest.mock('../../../error/src/ImperativeError'); describe('EventProcessor Unit Tests', () => { - const isSharedEventSpy = jest.spyOn(EventUtils, 'isSharedEvent'); - const isUserEventSpy = jest.spyOn(EventUtils, 'isUserEvent'); - const writeEventSpy = jest.spyOn(EventUtils, 'writeEvent'); - const createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription'); + let createSubscriptionSpy: any; + let setupWatcherSpy: any; + const appName = 'TestApp'; beforeEach(() => { jest.clearAllMocks(); + jest.spyOn(EventUtils, 'writeEvent').mockImplementation(jest.fn()); + createSubscriptionSpy = jest.spyOn(EventUtils, 'createSubscription').mockImplementation(jest.fn()); + setupWatcherSpy = jest.spyOn(EventUtils, 'setupWatcher').mockImplementation(jest.fn()); + + jest.spyOn(EventUtils, "getListOfApps").mockReturnValue(["Zowe", appName]); + const subs = EventProcessor.prototype.subscribedEvents = new Map(); + const dummyEvent: any = { subscriptions: [ { removeAllListeners: jest.fn().mockReturnValue({close: jest.fn()})} as any] } ; + subs.set("Zowe", dummyEvent); + }); + afterEach(() => { + EventOperator.deleteProcessor(appName); }); describe('Constructor', () => { it('initializes EventProcessor correctly', () => { - const appName = 'someApp'; - expect(EventOperator['instances'].get(appName)).toBeUndefined(); EventOperator.getProcessor(appName); @@ -45,78 +51,72 @@ describe('EventProcessor Unit Tests', () => { describe('Subscription Methods', () => { it('"subscribeShared" throws error for emitter-only processor', () => { - const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); - expect(() => emitter.subscribeShared('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); }); it('"subscribeUser" throws error for emitter-only processor', () => { - const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); - expect(() => emitter.subscribeUser('fakeEventToSubscribeTo', () => {})).toThrow(ImperativeError); }); it('"subscribeShared" correctly subscribes to shared events', () => { - const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); const eventName = 'someEvent'; const callbacks = [jest.fn()]; - isSharedEventSpy.mockReturnValue(true); createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); const disposable = processor.subscribeShared(eventName, callbacks); - expect(EventUtils.createSubscription).toHaveBeenCalledWith(processor, eventName, EventTypes.ZoweSharedEvents); - expect(EventUtils.setupWatcher).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(createSubscriptionSpy).toHaveBeenCalledWith(processor, eventName, EventTypes.SharedEvents); + expect(setupWatcherSpy).toHaveBeenCalledWith(processor, eventName, callbacks); expect(disposable).toBeDefined(); }); it('"subscribeUser" correctly subscribes to user-specific events', () => { - const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); const eventName = 'someEvent'; const callbacks = [jest.fn()]; - isUserEventSpy.mockReturnValue(true); createSubscriptionSpy.mockReturnValue({ close: jest.fn() }); const disposable = processor.subscribeUser(eventName, callbacks); - expect(EventUtils.createSubscription).toHaveBeenCalledWith(processor, eventName, EventTypes.ZoweUserEvents); - expect(EventUtils.setupWatcher).toHaveBeenCalledWith(processor, eventName, callbacks); + expect(createSubscriptionSpy).toHaveBeenCalledWith(processor, eventName, EventTypes.UserEvents); + expect(setupWatcherSpy).toHaveBeenCalledWith(processor, eventName, callbacks); expect(disposable).toBeDefined(); }); }); describe('Emission Methods', () => { it('"emitEvent" throws error for watcher-only processor', () => { - const appName = 'toSubscribeTo'; const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); - expect(() => watcher.emitEvent('someEvent')).toThrow(ImperativeError); }); + it('"emitEvent" throws error when emitting Zowe events', () => { + const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); + expect(() => emitter.emitEvent(ZoweUserEvents.ON_VAULT_CHANGED)).toThrow(ImperativeError); + expect(() => emitter.emitEvent(ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED)).toThrow(ImperativeError); + }); + it('"emitZoweEvent" throws error for watcher-only processor', () => { - const appName = 'toSubscribeTo'; const watcher = new EventProcessor(appName, IProcessorTypes.WATCHER); - expect(() => watcher.emitZoweEvent('someEvent')).toThrow(ImperativeError); }); + it('"emitZoweEvent" throws error when emitting non Zowe events', () => { + const emitter = EventOperator.getZoweProcessor(); + expect(() => emitter.emitZoweEvent('someEvent')).toThrow(ImperativeError); + }); + it('"emitEvent" updates event timestamp and writes event', () => { - const appName = 'toEmit'; const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); const eventName = 'someEvent'; const event = { eventTime: '', eventName, eventType: EventTypes.UserEvents, appName, subscriptions: new Set() } as unknown as Event; emitter.subscribedEvents.set(eventName, event); - isSharedEventSpy.mockReturnValue(false); - isUserEventSpy.mockReturnValue(false); - writeEventSpy.mockImplementation(() => {}); - emitter.emitEvent(eventName); expect(event.eventTime).not.toBe(''); @@ -126,7 +126,6 @@ describe('EventProcessor Unit Tests', () => { describe('Unsubscribe Methods', () => { it('"unsubscribe" removes subscriptions and cleans up resources', () => { - const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); processor.subscribedEvents = new Map([ ['testEvent', { @@ -148,7 +147,6 @@ describe('EventProcessor Unit Tests', () => { expect(processor.subscribedEvents.has('testEvent')).toBe(false); }); it('subscription removed from a processor\'s subscribed events and resources are cleaned', () => { - const appName = 'toSubscribeAndEmit'; const processor = new EventProcessor(appName, IProcessorTypes.BOTH); const mockSubscription = { removeAllListeners: jest.fn(), @@ -174,10 +172,7 @@ describe('EventProcessor Unit Tests', () => { expect(processor.subscribedEvents.has('testEvent')).toBe(false); }); it('"unsubscribe" throws error for emitter-only processor', () => { - const appName = 'toEmit'; - const emitter = new EventProcessor(appName, IProcessorTypes.EMITTER); - expect(() => emitter.unsubscribe('someEvent')).toThrow(ImperativeError); }); }); diff --git a/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts new file mode 100644 index 0000000000..f9633681ee --- /dev/null +++ b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts @@ -0,0 +1,153 @@ +/* +* 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 { EventOperator } from '../../src/EventOperator'; +import { EventProcessor } from '../../src/EventProcessor'; +import { Logger } from '../../..'; +import { IProcessorTypes } from '../../src/doc'; +import { Event } from '../../..'; +import { EventTypes, ZoweUserEvents } from "../../src/EventConstants"; + +jest.mock('../../src/EventProcessor'); +jest.mock('../../../logger'); + +const logger = Logger.getImperativeLogger(); + +describe("EventOperator Unit Tests", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("processor tests", () => { + it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { + const appName = 'TestApp'; + const type = IProcessorTypes.BOTH; + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, type, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'getZoweProcessor' should return the Zowe processor instance", () => { + const processor = EventOperator.getZoweProcessor(); + + expect(EventProcessor).toHaveBeenCalledWith("Zowe", IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it('emitZoweEvent is called by a Zowe processor and emits a ZoweUserEvents', () => { + const processor = EventOperator.getZoweProcessor(); + const eventName = "onVaultChanged"; + const emitZoweEventSpy = jest.spyOn(processor, 'emitZoweEvent'); + + processor.emitZoweEvent(eventName); + + expect(emitZoweEventSpy).toHaveBeenCalledWith(eventName); + expect(Object.values(ZoweUserEvents)).toContain(eventName); + }); + + it("'getProcessor' should return a generic event processor", () => { + const appName = 'GenericApp'; + const processor = EventOperator.getProcessor(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.BOTH, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteProcessor' should remove the correct event processor", () => { + const appName = 'DeleteApp'; + const processor = new EventProcessor(appName, IProcessorTypes.BOTH); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.SharedEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteProcessor(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("watcher tests", () => { + it("'getWatcher' should return a watcher-only event processor", () => { + const appName = 'WatcherApp'; + const processor = EventOperator.getWatcher(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.WATCHER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + it("'deleteWatcher' should remove the correct event processor", () => { + const appName = 'DeleteWatcher'; + const processor = new EventProcessor(appName, IProcessorTypes.WATCHER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteWatcher(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); + + describe("emitter tests", () => { + it("'getEmitter' should return an emitter-only event processor", () => { + const appName = 'EmitterApp'; + const processor = EventOperator.getEmitter(appName, logger); + + expect(EventProcessor).toHaveBeenCalledWith(appName, IProcessorTypes.EMITTER, logger); + expect(processor).toBeInstanceOf(EventProcessor); + }); + + it("'deleteEmitter' should remove the correct event processor", () => { + const appName = 'DeleteEmitter'; + const processor = new EventProcessor(appName, IProcessorTypes.EMITTER); + processor.subscribedEvents = new Map([ + ['testEvent', { + eventTime: '', + eventName: 'testEvent', + eventType: EventTypes.UserEvents, + appName: appName, + subscriptions: new Set([ + { + removeAllListeners: jest.fn(), + close: jest.fn() + } + ]) + } as unknown as Event] + ]); + + EventOperator.deleteEmitter(appName); + + expect(EventOperator['instances'].has(appName)).toBe(false); + }); + }); +}); \ No newline at end of file diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 6fafa49f44..1f16b52bbf 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -48,7 +48,6 @@ export class EventOperator { const procInstance = this.instances.get(appName); if (procInstance.processorType !== type) { procInstance.processorType = IProcessorTypes.BOTH; - // throw new ImperativeError({msg: "Not allowed to get the other hald"}) } return procInstance; } diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index c468a7ddc8..f277a5f357 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -92,6 +92,21 @@ export class EventProcessor { return disposable; } + /** + * Private method to emit the event + * @private + * @param eventName Event to be emitted + */ + private emit(eventName: string) { + try { + const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); + event.eventTime = new Date().toISOString(); + EventUtils.writeEvent(event); + } catch (err) { + throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); + } + } + /** * Emits an event by updating its timestamp and writing event data. * @@ -105,13 +120,7 @@ export class EventProcessor { if (EventUtils.isUserEvent(eventName) || EventUtils.isSharedEvent(eventName)) { throw new ImperativeError({ msg: `Processor not allowed to emit Zowe events: ${eventName}` }); } - try { - const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); - event.eventTime = new Date().toISOString(); - EventUtils.writeEvent(event); - } catch (err) { - throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); - } + this.emit(eventName); } /** @@ -122,20 +131,13 @@ export class EventProcessor { * @throws {ImperativeError} - If the event cannot be emitted. */ public emitZoweEvent(eventName: string): void { - if (this.appName === "Zowe") { - if (!EventUtils.isUserEvent(eventName) && !EventUtils.isSharedEvent(eventName)) { - throw new ImperativeError({ msg: `Invalid Zowe event: ${eventName}` }); - } - try { - const event = this.subscribedEvents.get(eventName) ?? EventUtils.createEvent(eventName, this.appName); - event.eventTime = new Date().toISOString(); - EventUtils.writeEvent(event); - } catch (err) { - throw new ImperativeError({ msg: `Error writing event: ${eventName}`, causeErrors: err }); - } - }else{ + if (this.appName !== "Zowe") { throw new ImperativeError({ msg: `Processor does not have Zowe permissions: ${eventName}` }); } + if (!EventUtils.isUserEvent(eventName) && !EventUtils.isSharedEvent(eventName)) { + throw new ImperativeError({ msg: `Invalid Zowe event: ${eventName}` }); + } + this.emit(eventName); } /** diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index bcd89dbf95..2a4ca377b6 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -35,26 +35,7 @@ export class EventUtils { */ public static getListOfApps(): string[] { const extendersJson = ConfigUtils.readExtendersJson(); - // We should not need to keep a reference to their sources return ["Zowe", ...Object.keys(extendersJson.profileTypes)]; - - /* - const apps: string[] = ["Zowe"]; // default application name - // Loop through each profile type and accumulate all names and their sources based on conditions. - for (const [profileType, details] of Object.entries(extendersJson.profileTypes)) { - // Check each entry in the 'from' array to decide if a tag is needed - details.from.forEach(item => { - if (item.includes("(for VS Code)")) { - apps.push(profileType, "_vsce"); // tag indicating Visual Studio Code Extension - } else if (item.includes("@zowe")) { - apps.push(profileType); // no tag indicates Zowe CLI plugin (default) - } else { - apps.push(profileType + "_custom") // tag indicating a true Custom App - } - }); - } - return apps; - */ } /** @@ -105,7 +86,7 @@ export class EventUtils { try { return JSON.parse(fs.readFileSync(eventFilePath).toString()); } catch (err) { - throw new ImperativeError({msg: `Unable to retrieve event contents: Path: ${eventFilePath}`}); + throw new ImperativeError({ msg: `Unable to retrieve event contents: Path: ${eventFilePath}` }); } } From 680d922cbcc85c204b4e3e137498092fcc899316 Mon Sep 17 00:00:00 2001 From: "Andrew W. Harn" Date: Thu, 20 Jun 2024 14:09:29 -0400 Subject: [PATCH 48/72] Add mock cleanup steps to unit tests Signed-off-by: Andrew W. Harn --- .../src/events/__tests__/__unit__/EventOperator.unit.test.ts | 3 +++ .../src/events/__tests__/__unit__/EventProcessor.unit.test.ts | 3 +++ .../src/events/__tests__/__unit__/EventUtils.unit.test.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts index 8c86fc1029..87063402ee 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventOperator.unit.test.ts @@ -35,6 +35,9 @@ describe("EventOperator Unit Tests", () => { EventOperator.deleteProcessor("Zowe"); EventOperator.deleteProcessor(appName); }); + afterAll(() => { + jest.restoreAllMocks(); + }); describe("processor tests", () => { it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { diff --git a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts index 9f24d4abcd..e98756b116 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventProcessor.unit.test.ts @@ -38,6 +38,9 @@ describe('EventProcessor Unit Tests', () => { afterEach(() => { EventOperator.deleteProcessor(appName); }); + afterAll(() => { + jest.restoreAllMocks(); + }); describe('Constructor', () => { it('initializes EventProcessor correctly', () => { diff --git a/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts index f9633681ee..7967ecda59 100644 --- a/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts +++ b/packages/imperative/src/events/__tests__/__unit__/EventUtils.unit.test.ts @@ -25,6 +25,9 @@ describe("EventOperator Unit Tests", () => { beforeEach(() => { jest.clearAllMocks(); }); + afterAll(() => { + jest.restoreAllMocks(); + }); describe("processor tests", () => { it("'createProcessor' should create a new 'EventProcessor' if not already existing", () => { From 09c2237489db873d4a63c28852c1543aedf0d2ff Mon Sep 17 00:00:00 2001 From: Fernando Rijo Cedeno Date: Thu, 20 Jun 2024 15:05:43 -0400 Subject: [PATCH 49/72] tests: fix integration test Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...Operator_and_Processor.integration.test.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index fe29c89b5d..ebc48a9270 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -9,7 +9,7 @@ * */ -import { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; import * as fs from "fs"; import * as path from "path"; import * as TestUtil from "../../../../__tests__/src/TestUtil"; @@ -18,8 +18,11 @@ import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/ let TEST_ENVIRONMENT: ITestEnvironment; const appName = "Zowe"; +const sampleApp = "sample"; const userHome = require('os').homedir(); -const zoweCliHome = process.env.ZOWE_CLI_HOME || ''; +const userEventsDir = path.join(userHome, '.zowe', '.events'); +let zoweCliHome: string; +let sharedEventsDir: string; describe("Event Operator and Processor", () => { @@ -28,15 +31,18 @@ describe("Event Operator and Processor", () => { cliHomeEnvVar: "ZOWE_CLI_HOME", testName: "event_operator_and_processor" }); + zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + sharedEventsDir = path.join(zoweCliHome, '.events'); + const extJson = ConfigUtils.readExtendersJson(); + extJson.profileTypes[sampleApp] = { from: [sampleApp] }; + ConfigUtils.writeExtendersJson(extJson); }); const cleanupDirectories = () => { - const userEventsDir = path.join(userHome, '.zowe', '.events'); if (fs.existsSync(userEventsDir)) { fs.rmdirSync(userEventsDir, { recursive: true }); } - const sharedEventsDir = path.join(zoweCliHome, '.events'); if (fs.existsSync(sharedEventsDir)) { fs.rmdirSync(sharedEventsDir, { recursive: true }); } @@ -45,7 +51,6 @@ describe("Event Operator and Processor", () => { afterEach(cleanupDirectories); afterAll(() => { - TestUtil.rimraf(userHome); TestUtil.rimraf(zoweCliHome); }); @@ -57,8 +62,9 @@ describe("Event Operator and Processor", () => { describe("Shared Events", () => { it("should create an event file upon first subscription if the file does not exist", () => { const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const theWatcher = EventOperator.getWatcher("sample"); + const theWatcher = EventOperator.getWatcher(appName); const theEmitter = EventOperator.getZoweProcessor(); + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const eventDir = path.join(zoweCliHome, '.events'); expect(doesEventFileExist(eventDir, theEvent)).toBeFalsy(); @@ -68,15 +74,17 @@ describe("Event Operator and Processor", () => { theWatcher.subscribeShared(theEvent, theCallback); expect(theCallback).not.toHaveBeenCalled(); - expect(doesEventFileExist(eventDir, theEvent)).toBeTruthy(); + expect(doesEventFileExist(path.join(eventDir, "Zowe"), theEvent)).toBeTruthy(); theEmitter.emitZoweEvent(theEvent); + (setupWatcherSpy.mock.calls[0][2] as Function)(); // Mock the event emission const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); expect(eventDetails.eventName).toEqual(theEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(theCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(sampleApp); EventOperator.deleteProcessor(appName); }); }); From 2b90cc278158a2cddf3056f8bedffc06b2a48453 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 24 Jun 2024 12:35:27 -0400 Subject: [PATCH 50/72] overriding previous integration tests Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 210 ++++++++++++++++-- 1 file changed, 186 insertions(+), 24 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index ebc48a9270..baa7180c5b 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -9,48 +9,40 @@ * */ -import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; -import * as fs from "fs"; -import * as path from "path"; -import * as TestUtil from "../../../../__tests__/src/TestUtil"; import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; +import { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import * as fs from "fs"; +import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; const appName = "Zowe"; -const sampleApp = "sample"; const userHome = require('os').homedir(); -const userEventsDir = path.join(userHome, '.zowe', '.events'); -let zoweCliHome: string; -let sharedEventsDir: string; +const zoweCliHome = process.env.ZOWE_CLI_HOME || ''; describe("Event Operator and Processor", () => { - beforeAll(async () => { TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ cliHomeEnvVar: "ZOWE_CLI_HOME", testName: "event_operator_and_processor" }); - zoweCliHome = process.env.ZOWE_CLI_HOME || ''; - sharedEventsDir = path.join(zoweCliHome, '.events'); - const extJson = ConfigUtils.readExtendersJson(); - extJson.profileTypes[sampleApp] = { from: [sampleApp] }; - ConfigUtils.writeExtendersJson(extJson); }); const cleanupDirectories = () => { if (fs.existsSync(userEventsDir)) { - fs.rmdirSync(userEventsDir, { recursive: true }); + fs.rmSync(userEventsDir, { recursive: true, force: true }); } + const sharedEventsDir = path.join(zoweCliHome, '.events'); if (fs.existsSync(sharedEventsDir)) { - fs.rmdirSync(sharedEventsDir, { recursive: true }); + fs.rmSync(sharedEventsDir, { recursive: true, force: true }); } }; afterEach(cleanupDirectories); afterAll(() => { + TestUtil.rimraf(userHome); TestUtil.rimraf(zoweCliHome); }); @@ -60,32 +52,202 @@ describe("Event Operator and Processor", () => { }; describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist", () => { + it("should create an event file upon first subscription if the file does not exist - ZOWE EVENT", async () => { const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; const theWatcher = EventOperator.getWatcher(appName); const theEmitter = EventOperator.getZoweProcessor(); - const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const eventDir = path.join(zoweCliHome, '.events'); - expect(doesEventFileExist(eventDir, theEvent)).toBeFalsy(); expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); - const theCallback = jest.fn() as EventCallback; + // Subscribe to the event theWatcher.subscribeShared(theEvent, theCallback); + const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); expect(theCallback).not.toHaveBeenCalled(); - expect(doesEventFileExist(path.join(eventDir, "Zowe"), theEvent)).toBeTruthy(); + expect(doesEventFileExist(eventDir, theEvent)).toBeTruthy(); + // Emit event and trigger callback theEmitter.emitZoweEvent(theEvent); (setupWatcherSpy.mock.calls[0][2] as Function)(); // Mock the event emission - const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); + // Adding a delay to ensure the callback has time to be called + await new Promise(resolve => setTimeout(resolve, 100)); + expect(eventDetails.eventName).toEqual(theEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(theCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(appName); + }); + + it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", async () => { + const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const firstWatcher = EventOperator.getWatcher(app1); + const secondWatcher = EventOperator.getWatcher(app2); + const theEmitter = EventOperator.getZoweProcessor(); + const eventDir = path.join(zoweCliHome, 'Zowe', '.events'); + const theFirstCallback: EventCallback = jest.fn() as EventCallback; + const theSecondCallback: EventCallback = jest.fn() as EventCallback; + + + expect((firstWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + + // Subscribe to the event + firstWatcher.subscribeShared(theEvent, theFirstCallback); + secondWatcher.subscribeShared(theEvent, theSecondCallback); + const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(theEvent).toJson(); + const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(theEvent).toJson(); + + // Emit event and trigger callbacks + theEmitter.emitZoweEvent(theEvent); + + // Adding a delay to ensure the callbacks have time to be called + await new Promise(resolve => setTimeout(resolve, 1000)); + + expect(firstEventDetails.eventName).toEqual(theEvent); + expect(secondEventDetails.eventName).toEqual(theEvent); + expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); + expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); + expect(theFirstCallback).toHaveBeenCalled(); + expect(theSecondCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(appName); + }); + + it("should not affect subscriptions from another instance when unsubscribing from events", async () => { + const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const firstProc = EventOperator.getZoweProcessor(); + const secondProc = EventOperator.getZoweProcessor(); + + const firstSubSpy = jest.fn(); + const secondSubSpy = jest.fn(); + + firstProc.subscribeShared(theEvent, firstSubSpy); + secondProc.subscribeShared(theEvent, secondSubSpy); + + firstProc.unsubscribe(theEvent); + + expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); + expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); + + // Emit event and trigger callbacks + console.log('Emitting event from secondProc'); + secondProc.emitZoweEvent(theEvent); + + // Adding a delay to ensure the callbacks have time to be called + await new Promise(resolve => setTimeout(resolve, 1000)); + + console.log('Checking if firstSubSpy has been called'); + expect(firstSubSpy).not.toHaveBeenCalled(); + console.log('Checking if secondSubSpy has been called'); + expect(secondSubSpy).toHaveBeenCalled(); EventOperator.deleteProcessor(sampleApp); EventOperator.deleteProcessor(appName); }); - }); + }); + + // describe("User Events", () => { + // it("should create an event file upon first subscription if the file does not exist", () => { + // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; + // const processor = EventOperator.getZoweProcessor(); + + // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeFalsy(); + // expect((processor as any).subscribedEvents.get(theEvent)).toBeFalsy(); + + // const subSpy = jest.fn(); + // processor.subscribeUser(theEvent, subSpy); + + // expect(subSpy).not.toHaveBeenCalled(); + // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); + + // processor.emitZoweEvent(theEvent); + + // (processor as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + + // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); + // const eventDetails: IEventJson = (processor as any).subscribedEvents.get(theEvent).toJson(); + // expect(eventDetails.eventName).toEqual(theEvent); + // expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeTruthy(); + + // expect(subSpy).toHaveBeenCalled(); + + // EventOperator.deleteProcessor(appName); + // }); + + // it("should trigger subscriptions for all instances watching for onVaultChanged", () => { + // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; + // const firstProc = EventOperator.getZoweProcessor(); + // const secondProc = EventOperator.getZoweProcessor(); + + // const firstSubSpy = jest.fn(); + // const secondSubSpy = jest.fn(); + + // firstProc.subscribeUser(theEvent, firstSubSpy); + // secondProc.subscribeUser(theEvent, secondSubSpy); + + // firstProc.emitZoweEvent(theEvent); + + // (firstProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + + // expect(firstSubSpy).toHaveBeenCalled(); + // expect(secondSubSpy).toHaveBeenCalled(); + + // EventOperator.deleteProcessor(appName); + // }); + + // it("should not affect subscriptions from another instance when unsubscribing from events", () => { + // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; + // const firstProc = EventOperator.getZoweProcessor(); + // const secondProc = EventOperator.getZoweProcessor(); + + // const firstSubSpy = jest.fn(); + // const secondSubSpy = jest.fn(); + + // firstProc.subscribeUser(theEvent, firstSubSpy); + // secondProc.subscribeUser(theEvent, secondSubSpy); + + // firstProc.unsubscribe(theEvent); + + // expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); + // expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); + + // secondProc.emitZoweEvent(theEvent); + + // (secondProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + + // expect(firstSubSpy).not.toHaveBeenCalled(); + // expect(secondSubSpy).toHaveBeenCalled(); + + // EventOperator.deleteProcessor(appName); + // }); + // }); + + // describe("Custom Events", () => { + // const customEvent = "onMyCustomEvent"; + + // it("should create an event file upon first subscription if the file does not exist", () => { + // const processor = EventOperator.getProcessor(appName); + + // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeFalsy(); + // expect((processor as any).subscribedEvents.get(customEvent)).toBeFalsy(); + + // const subSpy = jest.fn(); + // processor.subscribeShared(customEvent, subSpy); + + // expect(subSpy).not.toHaveBeenCalled(); + // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeTruthy(); + + // processor.emitEvent(customEvent); + + // (processor as any).subscribedEvents.get(customEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + + // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeTruthy(); + // const eventDetails: IEventJson = (processor as any).subscribedEvents.get(customEvent).toJson(); + // expect(eventDetails.eventName).toEqual(customEvent); + + // expect(subSpy).toHaveBeenCalled(); + + // EventOperator.deleteProcessor(appName); + // }); + // }); }); \ No newline at end of file From c06ecf2876ee2aca6a8f6b9f1ad9c32ed91598ab Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 24 Jun 2024 16:40:42 -0400 Subject: [PATCH 51/72] still fixing the intergration tests Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 256 ++++++++---------- 1 file changed, 111 insertions(+), 145 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index baa7180c5b..cdbcfe61bb 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -11,64 +11,74 @@ import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; -import { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, IWatcher, ZoweSharedEvents, ZoweUserEvents } from "../../.."; import * as fs from "fs"; import * as path from "path"; let TEST_ENVIRONMENT: ITestEnvironment; -const appName = "Zowe"; +const appName = "Zowe"; //the only guarenteed app name in the plugins list +const app1 = "Zowe";//"FakeWatcherApp1"; +const app2 = "Zowe";//"FakeWatcherApp2"; const userHome = require('os').homedir(); -const zoweCliHome = process.env.ZOWE_CLI_HOME || ''; +let zoweCliHome: string; +let eventDir: string; describe("Event Operator and Processor", () => { - beforeAll(async () => { - TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ - cliHomeEnvVar: "ZOWE_CLI_HOME", - testName: "event_operator_and_processor" - }); - }); + const doesEventFileExist = (eventDir: string, eventName: string) => { + const eventFilePath = path.join(eventDir, eventName); + return fs.existsSync(eventFilePath); + }; const cleanupDirectories = () => { + const userEventsDir = path.join(userHome, '.zowe', '.events'); if (fs.existsSync(userEventsDir)) { fs.rmSync(userEventsDir, { recursive: true, force: true }); } - const sharedEventsDir = path.join(zoweCliHome, '.events'); + const sharedEventsDir = path.join(zoweCliHome, 'Zowe', '.events'); if (fs.existsSync(sharedEventsDir)) { fs.rmSync(sharedEventsDir, { recursive: true, force: true }); } }; - afterEach(cleanupDirectories); - - afterAll(() => { - TestUtil.rimraf(userHome); - TestUtil.rimraf(zoweCliHome); + beforeEach(async () => { + TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_operator_and_processor" + }); + zoweCliHome = process.env.ZOWE_CLI_HOME || ''; }); - const doesEventFileExist = (eventDir: string, eventName: string) => { - const eventFilePath = path.join(eventDir, eventName); - return fs.existsSync(eventFilePath); - }; + afterEach(cleanupDirectories); describe("Shared Events", () => { - it("should create an event file upon first subscription if the file does not exist - ZOWE EVENT", async () => { - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const theWatcher = EventOperator.getWatcher(appName); - const theEmitter = EventOperator.getZoweProcessor(); + const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + let theWatcher: IWatcher; + let secondWatcher: IWatcher; + let theEmitter: any; + let theCallback: EventCallback; + let theSecondCallback: EventCallback; + + beforeEach(() => { + theWatcher = EventOperator.getWatcher(app1); + secondWatcher = EventOperator.getWatcher(app2); + theEmitter = EventOperator.getZoweProcessor(); + theCallback = jest.fn() as EventCallback; + theSecondCallback = jest.fn() as EventCallback; + }); + it("should create an event file upon first subscription if the file does not exist - ZOWE EVENT", async () => { expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); // Subscribe to the event theWatcher.subscribeShared(theEvent, theCallback); const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); - expect(theCallback).not.toHaveBeenCalled(); - expect(doesEventFileExist(eventDir, theEvent)).toBeTruthy(); + // Check for subscription evidence + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); // Emit event and trigger callback theEmitter.emitZoweEvent(theEvent); - (setupWatcherSpy.mock.calls[0][2] as Function)(); // Mock the event emission // Adding a delay to ensure the callback has time to be called await new Promise(resolve => setTimeout(resolve, 100)); @@ -79,150 +89,106 @@ describe("Event Operator and Processor", () => { EventOperator.deleteProcessor(appName); }); - it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", async () => { - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const firstWatcher = EventOperator.getWatcher(app1); - const secondWatcher = EventOperator.getWatcher(app2); - const theEmitter = EventOperator.getZoweProcessor(); - const eventDir = path.join(zoweCliHome, 'Zowe', '.events'); - const theFirstCallback: EventCallback = jest.fn() as EventCallback; - const theSecondCallback: EventCallback = jest.fn() as EventCallback; + // it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", async () => { + // expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + // expect((secondWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + // // Subscribe to the event + // theWatcher.subscribeShared(theEvent, theCallback); + // secondWatcher.subscribeShared(theEvent, theSecondCallback); + // const firstEventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); + // const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(theEvent).toJson(); - expect((firstWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); - expect((secondWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + // // Emit event and trigger callbacks + // theEmitter.emitZoweEvent(theEvent); - // Subscribe to the event - firstWatcher.subscribeShared(theEvent, theFirstCallback); - secondWatcher.subscribeShared(theEvent, theSecondCallback); - const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(theEvent).toJson(); - const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(theEvent).toJson(); + // // Adding a delay to ensure the callbacks have time to be called + // await new Promise(resolve => setTimeout(resolve, 1000)); - // Emit event and trigger callbacks - theEmitter.emitZoweEvent(theEvent); - - // Adding a delay to ensure the callbacks have time to be called - await new Promise(resolve => setTimeout(resolve, 1000)); - - expect(firstEventDetails.eventName).toEqual(theEvent); - expect(secondEventDetails.eventName).toEqual(theEvent); - expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); - expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); - expect(theFirstCallback).toHaveBeenCalled(); - expect(theSecondCallback).toHaveBeenCalled(); - EventOperator.deleteProcessor(appName); - }); + // expect(firstEventDetails.eventName).toEqual(theEvent); + // expect(secondEventDetails.eventName).toEqual(theEvent); + // expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); + // expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); + // expect(theCallback).toHaveBeenCalled(); + // expect(theSecondCallback).toHaveBeenCalled(); + // EventOperator.deleteProcessor(appName); + // }); it("should not affect subscriptions from another instance when unsubscribing from events", async () => { - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const firstProc = EventOperator.getZoweProcessor(); - const secondProc = EventOperator.getZoweProcessor(); - - const firstSubSpy = jest.fn(); - const secondSubSpy = jest.fn(); - - firstProc.subscribeShared(theEvent, firstSubSpy); - secondProc.subscribeShared(theEvent, secondSubSpy); - - firstProc.unsubscribe(theEvent); + theWatcher.subscribeShared(theEvent, theCallback); + secondWatcher.subscribeShared(theEvent, theSecondCallback); - expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); - expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); + // Check that subscribed + expect((theWatcher as any).subscribedEvents.get(theEvent)).toBeTruthy(); + expect((secondWatcher as any).subscribedEvents.get(theEvent)).toBeTruthy(); + secondWatcher.unsubscribe(theEvent); // Emit event and trigger callbacks - console.log('Emitting event from secondProc'); - secondProc.emitZoweEvent(theEvent); + theEmitter.emitZoweEvent(theEvent); // Adding a delay to ensure the callbacks have time to be called - await new Promise(resolve => setTimeout(resolve, 1000)); - - console.log('Checking if firstSubSpy has been called'); - expect(firstSubSpy).not.toHaveBeenCalled(); - console.log('Checking if secondSubSpy has been called'); - expect(secondSubSpy).toHaveBeenCalled(); + await new Promise(resolve => setTimeout(resolve, 100)); - EventOperator.deleteProcessor(sampleApp); + // Testing that only the watching processor has their callback triggered + expect(theCallback).toHaveBeenCalled(); + expect(theSecondCallback).not.toHaveBeenCalled(); EventOperator.deleteProcessor(appName); }); - }); - - // describe("User Events", () => { - // it("should create an event file upon first subscription if the file does not exist", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const processor = EventOperator.getZoweProcessor(); - - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeFalsy(); - // expect((processor as any).subscribedEvents.get(theEvent)).toBeFalsy(); - - // const subSpy = jest.fn(); - // processor.subscribeUser(theEvent, subSpy); - - // expect(subSpy).not.toHaveBeenCalled(); - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); - - // processor.emitZoweEvent(theEvent); - - // (processor as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); - // const eventDetails: IEventJson = (processor as any).subscribedEvents.get(theEvent).toJson(); - // expect(eventDetails.eventName).toEqual(theEvent); - // expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeTruthy(); - - // expect(subSpy).toHaveBeenCalled(); - - // EventOperator.deleteProcessor(appName); - // }); - - // it("should trigger subscriptions for all instances watching for onVaultChanged", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const firstProc = EventOperator.getZoweProcessor(); - // const secondProc = EventOperator.getZoweProcessor(); - - // const firstSubSpy = jest.fn(); - // const secondSubSpy = jest.fn(); - - // firstProc.subscribeUser(theEvent, firstSubSpy); - // secondProc.subscribeUser(theEvent, secondSubSpy); - - // firstProc.emitZoweEvent(theEvent); - - // (firstProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - - // expect(firstSubSpy).toHaveBeenCalled(); - // expect(secondSubSpy).toHaveBeenCalled(); + }); - // EventOperator.deleteProcessor(appName); - // }); + describe("User Events", () => { + const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; + let theWatcher: IWatcher; + let secondWatcher: IWatcher; + let theEmitter: any; + let theCallback: EventCallback; + let theSecondCallback: EventCallback; + + beforeEach(() => { + theWatcher = EventOperator.getWatcher(app1); + secondWatcher = EventOperator.getWatcher(app2); + theEmitter = EventOperator.getZoweProcessor(); + theCallback = jest.fn() as EventCallback; + theSecondCallback = jest.fn() as EventCallback; + zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + eventDir = path.join(zoweCliHome, 'Zowe', '.events'); + }); - // it("should not affect subscriptions from another instance when unsubscribing from events", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const firstProc = EventOperator.getZoweProcessor(); - // const secondProc = EventOperator.getZoweProcessor(); + it("should create an event file upon first subscription if the file does not exist", () => { + // File should not exist before first-time subscription + expect(doesEventFileExist(zoweCliHome, theEvent)).toBeFalsy(); + expect((theEmitter as any).subscribedEvents.get(theEvent)).toBeFalsy(); - // const firstSubSpy = jest.fn(); - // const secondSubSpy = jest.fn(); + // Subscribe to the event + theWatcher.subscribeShared(theEvent, theCallback); + const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); - // firstProc.subscribeUser(theEvent, firstSubSpy); - // secondProc.subscribeUser(theEvent, secondSubSpy); + // Check that file now exists + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); + EventOperator.deleteProcessor(appName); + }); - // firstProc.unsubscribe(theEvent); + it("should trigger subscriptions for all instances watching for onVaultChanged", () => { + const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; + const firstProc = EventOperator.getZoweProcessor(); + const secondProc = EventOperator.getZoweProcessor(); - // expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); - // expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); + const firstSubSpy = jest.fn(); + const secondSubSpy = jest.fn(); - // secondProc.emitZoweEvent(theEvent); + firstProc.subscribeUser(theEvent, firstSubSpy); + secondProc.subscribeUser(theEvent, secondSubSpy); - // (secondProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + firstProc.emitZoweEvent(theEvent); - // expect(firstSubSpy).not.toHaveBeenCalled(); - // expect(secondSubSpy).toHaveBeenCalled(); + (firstProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - // EventOperator.deleteProcessor(appName); - // }); - // }); + expect(firstSubSpy).toHaveBeenCalled(); + expect(secondSubSpy).toHaveBeenCalled(); - // describe("Custom Events", () => { + EventOperator.deleteProcessor(appName); + }); + }); // const customEvent = "onMyCustomEvent"; // it("should create an event file upon first subscription if the file does not exist", () => { From ebc9a4d6d369d0853d8af4c41440bc7b827d556b Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:09:56 -0400 Subject: [PATCH 52/72] test: fix final few integration tests Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...Operator_and_Processor.integration.test.ts | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index baa7180c5b..9c8d0f6ee3 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -11,14 +11,15 @@ import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; -import { EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; import * as fs from "fs"; import * as path from "path"; +import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; let TEST_ENVIRONMENT: ITestEnvironment; const appName = "Zowe"; -const userHome = require('os').homedir(); -const zoweCliHome = process.env.ZOWE_CLI_HOME || ''; +const sampleApps = ["sample1", "sample2"]; +let zoweCliHome: string; describe("Event Operator and Processor", () => { beforeAll(async () => { @@ -26,24 +27,17 @@ describe("Event Operator and Processor", () => { cliHomeEnvVar: "ZOWE_CLI_HOME", testName: "event_operator_and_processor" }); + zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); + sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); + ConfigUtils.writeExtendersJson(extJson); }); - - const cleanupDirectories = () => { - if (fs.existsSync(userEventsDir)) { - fs.rmSync(userEventsDir, { recursive: true, force: true }); - } - + afterEach(() => { const sharedEventsDir = path.join(zoweCliHome, '.events'); if (fs.existsSync(sharedEventsDir)) { fs.rmSync(sharedEventsDir, { recursive: true, force: true }); } - }; - - afterEach(cleanupDirectories); - - afterAll(() => { - TestUtil.rimraf(userHome); - TestUtil.rimraf(zoweCliHome); + jest.restoreAllMocks(); }); const doesEventFileExist = (eventDir: string, eventName: string) => { @@ -53,9 +47,12 @@ describe("Event Operator and Processor", () => { describe("Shared Events", () => { it("should create an event file upon first subscription if the file does not exist - ZOWE EVENT", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const theCallback = jest.fn(); const theWatcher = EventOperator.getWatcher(appName); const theEmitter = EventOperator.getZoweProcessor(); + const eventDir = path.join(zoweCliHome, ".events"); expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); @@ -64,11 +61,11 @@ describe("Event Operator and Processor", () => { const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); expect(theCallback).not.toHaveBeenCalled(); - expect(doesEventFileExist(eventDir, theEvent)).toBeTruthy(); + expect(doesEventFileExist(path.join(eventDir, "Zowe"), theEvent)).toBeTruthy(); // Emit event and trigger callback theEmitter.emitZoweEvent(theEvent); - (setupWatcherSpy.mock.calls[0][2] as Function)(); // Mock the event emission + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock the event emission // Adding a delay to ensure the callback has time to be called await new Promise(resolve => setTimeout(resolve, 100)); @@ -80,15 +77,15 @@ describe("Event Operator and Processor", () => { }); it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const firstWatcher = EventOperator.getWatcher(app1); - const secondWatcher = EventOperator.getWatcher(app2); + const firstWatcher = EventOperator.getWatcher(sampleApps[0]); + const secondWatcher = EventOperator.getWatcher(sampleApps[1]); const theEmitter = EventOperator.getZoweProcessor(); - const eventDir = path.join(zoweCliHome, 'Zowe', '.events'); + const theFirstCallback: EventCallback = jest.fn() as EventCallback; const theSecondCallback: EventCallback = jest.fn() as EventCallback; - expect((firstWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); expect((secondWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); @@ -102,7 +99,8 @@ describe("Event Operator and Processor", () => { theEmitter.emitZoweEvent(theEvent); // Adding a delay to ensure the callbacks have time to be called - await new Promise(resolve => setTimeout(resolve, 1000)); + // await new Promise(resolve => setTimeout(resolve, 1000)); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock the event emission expect(firstEventDetails.eventName).toEqual(theEvent); expect(secondEventDetails.eventName).toEqual(theEvent); @@ -110,12 +108,15 @@ describe("Event Operator and Processor", () => { expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); expect(theFirstCallback).toHaveBeenCalled(); expect(theSecondCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(sampleApps[0]); + EventOperator.deleteProcessor(sampleApps[1]); EventOperator.deleteProcessor(appName); }); it("should not affect subscriptions from another instance when unsubscribing from events", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const firstProc = EventOperator.getZoweProcessor(); + const firstProc = EventOperator.getProcessor(sampleApps[0]); const secondProc = EventOperator.getZoweProcessor(); const firstSubSpy = jest.fn(); @@ -130,21 +131,21 @@ describe("Event Operator and Processor", () => { expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); // Emit event and trigger callbacks - console.log('Emitting event from secondProc'); secondProc.emitZoweEvent(theEvent); // Adding a delay to ensure the callbacks have time to be called - await new Promise(resolve => setTimeout(resolve, 1000)); + // await new Promise(resolve => setTimeout(resolve, 1000)); + setupWatcherSpy.mock.calls.forEach(call => { + if (call[0].appName === appName) { (call[2] as Function)() } + }); // Mock the event emission - console.log('Checking if firstSubSpy has been called'); expect(firstSubSpy).not.toHaveBeenCalled(); - console.log('Checking if secondSubSpy has been called'); expect(secondSubSpy).toHaveBeenCalled(); - EventOperator.deleteProcessor(sampleApp); + EventOperator.deleteProcessor(sampleApps[0]); EventOperator.deleteProcessor(appName); }); - }); + }); // describe("User Events", () => { // it("should create an event file upon first subscription if the file does not exist", () => { From e356b65fa83b0485a647b956b4ca9ddb4c4aa6ce Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:52:18 -0400 Subject: [PATCH 53/72] lint: address lint errors Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../__tests__/Config.secure.unit.test.ts | 1 - .../config/__tests__/ConfigUtils.unit.test.ts | 118 +++++++++++++++++- .../ProfileInfo.TeamConfig.unit.test.ts | 116 +---------------- .../imperative/src/config/src/ProfileInfo.ts | 2 +- ...Operator_and_Processor.integration.test.ts | 5 +- .../utilities/npm-interface/uninstall.ts | 4 +- 6 files changed, 123 insertions(+), 123 deletions(-) diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index 82374551dc..d7188a514a 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -18,7 +18,6 @@ import { Config } from "../src/Config"; import { IConfig } from "../src/doc/IConfig"; import { IConfigSecure } from "../src/doc/IConfigSecure"; import { IConfigVault } from "../src/doc/IConfigVault"; -import { EventOperator } from "../../events"; const MY_APP = "my_app"; diff --git a/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts b/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts index ed5a5b6b14..ae94f0e098 100644 --- a/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ConfigUtils.unit.test.ts @@ -10,12 +10,20 @@ */ import * as fs from "fs"; - +import * as path from "path"; +import * as os from "os"; +import * as jsonfile from "jsonfile"; import { ConfigUtils } from "../../config/src/ConfigUtils"; import { CredentialManagerFactory } from "../../security"; import { ImperativeConfig } from "../../utilities"; +import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; +import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; describe("Config Utils", () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + describe("coercePropValue", () => { it("should parse value when type is boolean", () => { expect(ConfigUtils.coercePropValue("false", "boolean")).toBe(false); @@ -149,4 +157,112 @@ describe("Config Utils", () => { expect(fsExistsSyncSpy).toHaveBeenCalledTimes(1); }); }); + + describe("getZoweDir", () => { + const expectedLoadedConfig = { + name: "zowe", + defaultHome: path.join("z", "zowe"), + envVariablePrefix: "ZOWE" + }; + let defaultHome: string; + let envReadSpy: any; + let homeDirSpy: any; + let loadedConfigOrig: any; + + beforeAll(() => { + loadedConfigOrig = ImperativeConfig.instance.loadedConfig; + }); + + beforeEach(() => { + envReadSpy = jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ + cliHome: { value: null } + } as any); + homeDirSpy = jest.spyOn(os, "homedir").mockReturnValue(expectedLoadedConfig.defaultHome); + ImperativeConfig.instance.loadedConfig = undefined as any; + defaultHome = path.join(expectedLoadedConfig.defaultHome, ".zowe"); + }); + + afterAll(() => { + ImperativeConfig.instance.loadedConfig = loadedConfigOrig; + envReadSpy.mockRestore(); + homeDirSpy.mockRestore(); + }); + + it("should return the ENV cliHome even if loadedConfig is set in the process", () => { + jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ cliHome: { value: "test" } } as any); + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + expect(ConfigUtils.getZoweDir()).toEqual("test"); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome and set loadedConfig if undefined", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome and reset loadedConfig if defaultHome changes", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + ImperativeConfig.instance.loadedConfig = { ...expectedLoadedConfig, defaultHome: "test" }; + expect(ImperativeConfig.instance.loadedConfig?.defaultHome).toEqual("test"); + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + + it("should return the defaultHome without resetting loadedConfig", () => { + expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); + ImperativeConfig.instance.loadedConfig = expectedLoadedConfig; + expect(ConfigUtils.getZoweDir()).toEqual(defaultHome); + expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); + }); + }); + + const dummyExtJson: IExtendersJsonOpts = { + profileTypes: { + "test": { + from: ["Zowe Client App"] + } + } + }; + describe("readExtendersJsonFromDisk", () => { + // case 1: the JSON file doesn't exist at time of read + it("writes an empty extenders.json file if it doesn't exist on disk", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); + ConfigUtils.readExtendersJson(); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + + // case 2: JSON file exists on-disk at time of read + it("reads extenders.json from disk if it exists", async () => { + const readFileSyncMock = jest.spyOn(jsonfile, "readFileSync").mockReturnValueOnce(dummyExtJson); + jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); + const result = ConfigUtils.readExtendersJson(); + expect(readFileSyncMock).toHaveBeenCalled(); + expect(result).toEqual({ + profileTypes: { + "test": { + from: ["Zowe Client App"] + } + } + }); + }); + }); + + describe("writeExtendersJson", () => { + // case 1: Write operation is successful + it("returns true if written to disk successfully", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + expect(ConfigUtils.writeExtendersJson(dummyExtJson)).toBe(true); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + + // case 2: Write operation is unsuccessful + it("returns false if it couldn't write to disk", async () => { + const writeFileSyncMock = jest.spyOn(jsonfile, "writeFileSync").mockImplementation(); + writeFileSyncMock.mockImplementation(() => { throw new Error(); }); + expect(ConfigUtils.writeExtendersJson(dummyExtJson)).toBe(false); + expect(writeFileSyncMock).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index d81b1839f8..cdc9cad898 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -10,7 +10,6 @@ */ import * as fs from "fs"; -import * as os from "os"; import * as path from "path"; import * as jsonfile from "jsonfile"; import * as lodash from "lodash"; @@ -25,7 +24,6 @@ import { ProfLocType } from "../src/doc/IProfLoc"; import { IProfileSchema } from "../../profiles"; import { AbstractSession, SessConstants } from "../../rest"; import { ConfigAutoStore } from "../src/ConfigAutoStore"; -import { EnvironmentalVariableSettings } from "../../imperative/src/env/EnvironmentalVariableSettings"; import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; import { ImperativeError } from "../../error"; import { IProfInfoUpdatePropOpts } from "../src/doc/IProfInfoUpdatePropOpts"; @@ -34,7 +32,6 @@ import { ConfigProfiles } from "../src/api"; import { IExtendersJsonOpts } from "../src/doc/IExtenderOpts"; import { ConfigSchema } from "../src/ConfigSchema"; import { Logger } from "../../logger/src/Logger"; -import { EventOperator, EventUtils } from "../../events"; const testAppNm = "ProfInfoApp"; @@ -154,65 +151,6 @@ describe("TeamConfig ProfileInfo tests", () => { }); }); - describe("getZoweDir", () => { - const expectedLoadedConfig = { - name: "zowe", - defaultHome: path.join("z", "zowe"), - envVariablePrefix: "ZOWE" - }; - let defaultHome: string; - let envReadSpy: any; - let homeDirSpy: any; - let loadedConfigOrig: any; - - beforeAll(() => { - loadedConfigOrig = ImperativeConfig.instance.loadedConfig; - }); - - beforeEach(() => { - envReadSpy = jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ - cliHome: { value: null } - } as any); - homeDirSpy = jest.spyOn(os, "homedir").mockReturnValue(expectedLoadedConfig.defaultHome); - ImperativeConfig.instance.loadedConfig = undefined as any; - defaultHome = path.join(expectedLoadedConfig.defaultHome, ".zowe"); - }); - - afterAll(() => { - ImperativeConfig.instance.loadedConfig = loadedConfigOrig; - envReadSpy.mockRestore(); - homeDirSpy.mockRestore(); - }); - - it("should return the ENV cliHome even if loadedConfig is set in the process", () => { - jest.spyOn(EnvironmentalVariableSettings, "read").mockReturnValue({ cliHome: { value: "test" } } as any); - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - expect(ProfileInfo.getZoweDir()).toEqual("test"); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome and set loadedConfig if undefined", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome and reset loadedConfig if defaultHome changes", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - ImperativeConfig.instance.loadedConfig = { ...expectedLoadedConfig, defaultHome: "test" }; - expect(ImperativeConfig.instance.loadedConfig?.defaultHome).toEqual("test"); - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - - it("should return the defaultHome without resetting loadedConfig", () => { - expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - ImperativeConfig.instance.loadedConfig = expectedLoadedConfig; - expect(ProfileInfo.getZoweDir()).toEqual(defaultHome); - expect(ImperativeConfig.instance.loadedConfig).toEqual({ ...expectedLoadedConfig, defaultHome }); - }); - }); - describe("createSession", () => { const profAttrs: IProfAttrs = { profName: "profName", @@ -1476,58 +1414,6 @@ describe("TeamConfig ProfileInfo tests", () => { }; // begin schema management tests - describe("readExtendersJsonFromDisk", () => { - // case 1: the JSON file doesn't exist at time of read - it("writes an empty extenders.json file if it doesn't exist on disk", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - jest.spyOn(fs, "existsSync").mockReturnValueOnce(false); - ProfileInfo.readExtendersJsonFromDisk(); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - - // case 2: JSON file exists on-disk at time of read - it("reads extenders.json from disk if it exists", async () => { - const readFileSyncMock = jest.spyOn(jsonfile, "readFileSync").mockReturnValueOnce({ profileTypes: { - "test": { - from: ["Zowe Client App"] - } - } }); - const profInfo = createNewProfInfo(teamProjDir); - jest.spyOn(fs, "existsSync").mockReturnValueOnce(true); - (profInfo as any).mExtendersJson = ProfileInfo.readExtendersJsonFromDisk(); - expect(readFileSyncMock).toHaveBeenCalled(); - expect((profInfo as any).mExtendersJson).toEqual({ - profileTypes: { - "test": { - from: ["Zowe Client App"] - } - } - }); - }); - }); - - describe("writeExtendersJson", () => { - // case 1: Write operation is successful - it("returns true if written to disk successfully", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - await profInfo.readProfilesFromDisk({ homeDir: teamHomeProjDir }); - expect(ProfileInfo.writeExtendersJson((profInfo as any).mExtendersJson)).toBe(true); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - - // case 2: Write operation is unsuccessful - it("returns false if it couldn't write to disk", async () => { - const profInfo = createNewProfInfo(teamProjDir); - (profInfo as any).mExtendersJson = { profileTypes: {} }; - await profInfo.readProfilesFromDisk({ homeDir: teamHomeProjDir }); - writeFileSyncMock.mockImplementation(() => { throw new Error(); }); - expect(ProfileInfo.writeExtendersJson((profInfo as any).mExtendersJson)).toBe(false); - expect(writeFileSyncMock).toHaveBeenCalled(); - }); - }); - describe("updateSchemaAtLayer", () => { const getBlockMocks = () => { return { @@ -1679,7 +1565,7 @@ describe("TeamConfig ProfileInfo tests", () => { } as any); const updateSchemaAtLayerMock = jest.spyOn((ProfileInfo as any).prototype, "updateSchemaAtLayer") .mockReturnValue(expected.res.success); - const writeExtendersJsonMock = jest.spyOn(ProfileInfo, "writeExtendersJson").mockImplementation(); + const writeExtendersJsonMock = jest.spyOn(ConfigUtils, "writeExtendersJson").mockImplementation(); const res = profInfo.addProfileTypeToSchema("some-type", { ...testCase, sourceApp: "Zowe Client App" }); if (expected.res.success) { expect(updateSchemaAtLayerMock).toHaveBeenCalled(); diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 710fa90de3..94321ae6bd 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1262,7 +1262,7 @@ export class ProfileInfo { // Update contents of extenders.json if it has changed if (wasGlobalUpdated && !lodash.isEqual(oldExtendersJson, this.mExtendersJson)) { - if (!ProfileInfo.writeExtendersJson(this.mExtendersJson)) { + if (!ConfigUtils.writeExtendersJson(this.mExtendersJson)) { return { success: true, // Even if we failed to update extenders.json, it was technically added to the schema cache. diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 9c8d0f6ee3..303c46e9e5 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -16,14 +16,13 @@ import * as fs from "fs"; import * as path from "path"; import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; -let TEST_ENVIRONMENT: ITestEnvironment; const appName = "Zowe"; const sampleApps = ["sample1", "sample2"]; let zoweCliHome: string; describe("Event Operator and Processor", () => { beforeAll(async () => { - TEST_ENVIRONMENT = await SetupTestEnvironment.createTestEnv({ + await SetupTestEnvironment.createTestEnv({ cliHomeEnvVar: "ZOWE_CLI_HOME", testName: "event_operator_and_processor" }); @@ -136,7 +135,7 @@ describe("Event Operator and Processor", () => { // Adding a delay to ensure the callbacks have time to be called // await new Promise(resolve => setTimeout(resolve, 1000)); setupWatcherSpy.mock.calls.forEach(call => { - if (call[0].appName === appName) { (call[2] as Function)() } + if (call[0].appName === appName) { (call[2] as Function)(); } }); // Mock the event emission expect(firstSubSpy).not.toHaveBeenCalled(); diff --git a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts index b400c1e5c5..90e001720d 100644 --- a/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts +++ b/packages/imperative/src/imperative/src/plugins/utilities/npm-interface/uninstall.ts @@ -19,7 +19,7 @@ import { ImperativeError } from "../../../../../error"; import { ExecUtils, TextUtils } from "../../../../../utilities"; import { StdioOptions } from "child_process"; import { findNpmOnPath } from "../NpmFunctions"; -import { ConfigSchema, ConfigUtils, ProfileInfo } from "../../../../../config"; +import { ConfigSchema, ConfigUtils } from "../../../../../config"; import { IProfileTypeConfiguration } from "../../../../../profiles"; const npmCmd = findNpmOnPath(); @@ -59,7 +59,7 @@ export const updateAndGetRemovedTypes = (npmPackage: string): string[] => { typesToRemove.push(profileType); } } - ProfileInfo.writeExtendersJson(extendersJson); + ConfigUtils.writeExtendersJson(extendersJson); } return typesToRemove; From 0dc8be6195a997c05d1bd4c914c7c919e1cef61f Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 10:52:43 -0400 Subject: [PATCH 54/72] slimmed down integration tests Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 306 +++++++----------- 1 file changed, 119 insertions(+), 187 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 30e16277ee..c08422bda3 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -1,17 +1,16 @@ /* -* This program and the accompanying materials are made available under the terms of the +* This program and accompanying materials are made available under terms of * 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. +* Copyright Contributors to Zowe Project. * */ -import { ITestEnvironment } from "../../../../__tests__/__src__/environment/doc/response/ITestEnvironment"; import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; -import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; +import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; import * as fs from "fs"; import * as path from "path"; import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; @@ -20,111 +19,95 @@ const appName = "Zowe"; const sampleApps = ["sample1", "sample2"]; let zoweCliHome: string; -describe("Event Operator and Processor", () => { - beforeAll(async () => { - await SetupTestEnvironment.createTestEnv({ - cliHomeEnvVar: "ZOWE_CLI_HOME", - testName: "event_operator_and_processor" - }); - zoweCliHome = process.env.ZOWE_CLI_HOME || ''; - const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); - sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); - ConfigUtils.writeExtendersJson(extJson); - }); - afterEach(() => { - const sharedEventsDir = path.join(zoweCliHome, '.events'); - if (fs.existsSync(sharedEventsDir)) { - fs.rmSync(sharedEventsDir, { recursive: true, force: true }); - } - jest.restoreAllMocks(); +beforeAll(async () => { + await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_operator_and_processor" }); + zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); + sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); + ConfigUtils.writeExtendersJson(extJson); +}); + +beforeEach(() => { + jest.restoreAllMocks(); +}); + +afterEach(() => { + const sharedEventsDir = path.join(zoweCliHome, '.events'); + if (fs.existsSync(sharedEventsDir)) { + fs.rmSync(sharedEventsDir, { recursive: true, force: true }); + } +}); + +const doesEventFileExist = (eventDir: string, eventName: string) => { + const eventFilePath = path.join(eventDir, eventName); + return fs.existsSync(eventFilePath); +}; - const doesEventFileExist = (eventDir: string, eventName: string) => { - const eventFilePath = path.join(eventDir, eventName); - return fs.existsSync(eventFilePath); - }; - - describe("Shared Events", () => { - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - let theWatcher: IWatcher; - let secondWatcher: IWatcher; - let theEmitter: any; - let theCallback: EventCallback; - let theSecondCallback: EventCallback; - - beforeEach(() => { - theWatcher = EventOperator.getWatcher(app1); - secondWatcher = EventOperator.getWatcher(app2); - theEmitter = EventOperator.getZoweProcessor(); - theCallback = jest.fn() as EventCallback; - theSecondCallback = jest.fn() as EventCallback; - }); +describe("Event Operator and Processor", () => { + const userEvent = ZoweUserEvents.ON_VAULT_CHANGED; + const customEvent = "onCustomEvent"; + const sharedEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - it("should create an event file upon first subscription if the file does not exist - ZOWE EVENT", async () => { + describe("Zowe Events - Shared", () => { + it("should create an event file upon first subscription if file does not exist", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const theCallback = jest.fn(); - const theWatcher = EventOperator.getWatcher(appName); - const theEmitter = EventOperator.getZoweProcessor(); + const callback = jest.fn(); const eventDir = path.join(zoweCliHome, ".events"); + const Watcher = EventOperator.getWatcher(appName); + const Emitter = EventOperator.getZoweProcessor(); - expect((theWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + expect((Watcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); - // Subscribe to the event - theWatcher.subscribeShared(theEvent, theCallback); - const eventDetails: IEventJson = (theWatcher as any).subscribedEvents.get(theEvent).toJson(); + // Subscribe to event + Watcher.subscribeShared(sharedEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(sharedEvent).toJson(); - expect(theCallback).not.toHaveBeenCalled(); - expect(doesEventFileExist(path.join(eventDir, "Zowe"), theEvent)).toBeTruthy(); + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); // Emit event and trigger callback - theEmitter.emitZoweEvent(theEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock the event emission - - // Adding a delay to ensure the callback has time to be called - await new Promise(resolve => setTimeout(resolve, 100)); + Emitter.emitZoweEvent(sharedEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - expect(eventDetails.eventName).toEqual(theEvent); + expect(eventDetails.eventName).toEqual(sharedEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); - expect(theCallback).toHaveBeenCalled(); + expect(callback).toHaveBeenCalled(); EventOperator.deleteProcessor(appName); }); - it("should trigger subscriptions for all instances watching for onCredentialManagerChanged", async () => { + it("should trigger subscriptions for all instances watching for ZoweSharedEvent", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; const firstWatcher = EventOperator.getWatcher(sampleApps[0]); const secondWatcher = EventOperator.getWatcher(sampleApps[1]); - const theEmitter = EventOperator.getZoweProcessor(); + const Emitter = EventOperator.getZoweProcessor(); - const theFirstCallback: EventCallback = jest.fn() as EventCallback; - const theSecondCallback: EventCallback = jest.fn() as EventCallback; + const FirstCallback: EventCallback = jest.fn() as EventCallback; + const SecondCallback: EventCallback = jest.fn() as EventCallback; - expect((firstWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); - expect((secondWatcher as EventProcessor).subscribedEvents.get(theEvent)).toBeFalsy(); + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); - // Subscribe to the event - firstWatcher.subscribeShared(theEvent, theFirstCallback); - secondWatcher.subscribeShared(theEvent, theSecondCallback); - - // Check that subscribed - expect((theWatcher as any).subscribedEvents.get(theEvent)).toBeTruthy(); - expect((secondWatcher as any).subscribedEvents.get(theEvent)).toBeTruthy(); - secondWatcher.unsubscribe(theEvent); + // Subscribe to event + firstWatcher.subscribeShared(sharedEvent, FirstCallback); + secondWatcher.subscribeShared(sharedEvent, SecondCallback); + const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedEvent).toJson(); + const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedEvent).toJson(); // Emit event and trigger callbacks - theEmitter.emitZoweEvent(theEvent); + Emitter.emitZoweEvent(sharedEvent); - // Adding a delay to ensure the callbacks have time to be called - // await new Promise(resolve => setTimeout(resolve, 1000)); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock the event emission + // Adding a delay to ensure callbacks have time to be called + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - expect(firstEventDetails.eventName).toEqual(theEvent); - expect(secondEventDetails.eventName).toEqual(theEvent); + expect(firstEventDetails.eventName).toEqual(sharedEvent); + expect(secondEventDetails.eventName).toEqual(sharedEvent); expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); - expect(theFirstCallback).toHaveBeenCalled(); - expect(theSecondCallback).toHaveBeenCalled(); + expect(FirstCallback).toHaveBeenCalled(); + expect(SecondCallback).toHaveBeenCalled(); EventOperator.deleteProcessor(sampleApps[0]); EventOperator.deleteProcessor(sampleApps[1]); EventOperator.deleteProcessor(appName); @@ -132,29 +115,27 @@ describe("Event Operator and Processor", () => { it("should not affect subscriptions from another instance when unsubscribing from events", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const theEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const firstProc = EventOperator.getProcessor(sampleApps[0]); + const firstWatcher = EventOperator.getWatcher(sampleApps[0]); const secondProc = EventOperator.getZoweProcessor(); const firstSubSpy = jest.fn(); const secondSubSpy = jest.fn(); - firstProc.subscribeUser(theEvent, firstSubSpy); - secondProc.subscribeUser(theEvent, secondSubSpy); + firstWatcher.subscribeShared(sharedEvent, firstSubSpy); + secondProc.subscribeShared(sharedEvent, secondSubSpy); - firstProc.emitZoweEvent(theEvent); + firstWatcher.unsubscribe(sharedEvent); - expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); - expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); + expect((firstWatcher as any).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((secondProc as any).subscribedEvents.get(sharedEvent)).toBeTruthy(); // Emit event and trigger callbacks - secondProc.emitZoweEvent(theEvent); + secondProc.emitZoweEvent(sharedEvent); - // Adding a delay to ensure the callbacks have time to be called - // await new Promise(resolve => setTimeout(resolve, 1000)); + // Adding a delay to ensure callbacks have time to be called setupWatcherSpy.mock.calls.forEach(call => { if (call[0].appName === appName) { (call[2] as Function)(); } - }); // Mock the event emission + }); // Mock event emission expect(firstSubSpy).not.toHaveBeenCalled(); expect(secondSubSpy).toHaveBeenCalled(); @@ -164,108 +145,59 @@ describe("Event Operator and Processor", () => { }); }); - // describe("User Events", () => { - // it("should create an event file upon first subscription if the file does not exist", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const processor = EventOperator.getZoweProcessor(); - - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeFalsy(); - // expect((processor as any).subscribedEvents.get(theEvent)).toBeFalsy(); - - // const subSpy = jest.fn(); - // processor.subscribeUser(theEvent, subSpy); - - // expect(subSpy).not.toHaveBeenCalled(); - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); - - // processor.emitZoweEvent(theEvent); - - // (processor as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - - // expect(doesEventFileExist(zoweCliHome, theEvent)).toBeTruthy(); - // const eventDetails: IEventJson = (processor as any).subscribedEvents.get(theEvent).toJson(); - // expect(eventDetails.eventName).toEqual(theEvent); - // expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeTruthy(); - - // expect(subSpy).toHaveBeenCalled(); - - // EventOperator.deleteProcessor(appName); - // }); - - // it("should trigger subscriptions for all instances watching for onVaultChanged", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const firstProc = EventOperator.getZoweProcessor(); - // const secondProc = EventOperator.getZoweProcessor(); - - // const firstSubSpy = jest.fn(); - // const secondSubSpy = jest.fn(); - - // firstProc.subscribeUser(theEvent, firstSubSpy); - // secondProc.subscribeUser(theEvent, secondSubSpy); - - // firstProc.emitZoweEvent(theEvent); - - // (firstProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - - // expect(firstSubSpy).toHaveBeenCalled(); - // expect(secondSubSpy).toHaveBeenCalled(); - - // EventOperator.deleteProcessor(appName); - // }); - - // it("should not affect subscriptions from another instance when unsubscribing from events", () => { - // const theEvent = ZoweUserEvents.ON_VAULT_CHANGED; - // const firstProc = EventOperator.getZoweProcessor(); - // const secondProc = EventOperator.getZoweProcessor(); - - // const firstSubSpy = jest.fn(); - // const secondSubSpy = jest.fn(); - - // firstProc.subscribeUser(theEvent, firstSubSpy); - // secondProc.subscribeUser(theEvent, secondSubSpy); - - // firstProc.unsubscribe(theEvent); - - // expect((firstProc as any).subscribedEvents.get(theEvent)).toBeFalsy(); - // expect((secondProc as any).subscribedEvents.get(theEvent)).toBeTruthy(); - - // secondProc.emitZoweEvent(theEvent); - - // (secondProc as any).subscribedEvents.get(theEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called - - // expect(firstSubSpy).not.toHaveBeenCalled(); - // expect(secondSubSpy).toHaveBeenCalled(); + describe("Zowe Events - User", () => { + it("should create an event file upon first subscription if file does not exist - specific to user event directory structure", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const eventDir = path.join(zoweCliHome, ".events"); + const Watcher = EventOperator.getWatcher(sampleApps[0]); + const Emitter = EventOperator.getZoweProcessor(); - // EventOperator.deleteProcessor(appName); - // }); - // }); + expect((Watcher as EventProcessor).subscribedEvents.get(userEvent)).toBeFalsy(); - // describe("Custom Events", () => { - // const customEvent = "onMyCustomEvent"; + // Subscribe to event + Watcher.subscribeShared(userEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(userEvent).toJson(); - // it("should create an event file upon first subscription if the file does not exist", () => { - // const processor = EventOperator.getProcessor(appName); + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); - // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeFalsy(); - // expect((processor as any).subscribedEvents.get(customEvent)).toBeFalsy(); + // Emit event and trigger callback + Emitter.emitZoweEvent(userEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - // const subSpy = jest.fn(); - // processor.subscribeShared(customEvent, subSpy); + expect(eventDetails.eventName).toEqual(userEvent); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(callback).toHaveBeenCalled(); + EventOperator.deleteProcessor(appName); + }); + }); - // expect(subSpy).not.toHaveBeenCalled(); - // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeTruthy(); + describe("Custom Events", () => { + it("should create an event file upon first subscription if file does not exist - specific to custom event directory structure", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const Watcher = EventOperator.getWatcher(sampleApps[0]); + const Emitter = EventOperator.getZoweProcessor(); + const eventDir = path.join(zoweCliHome,sampleApps[0], ".events"); - // processor.emitEvent(customEvent); + expect((Watcher as EventProcessor).subscribedEvents.get(customEvent)).toBeFalsy(); - // (processor as any).subscribedEvents.get(customEvent).subscriptions.forEach((sub: any) => sub()); // simulate FSWatcher called + // Subscribe to event + Watcher.subscribeShared(customEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customEvent).toJson(); - // expect(doesEventFileExist(zoweCliHome, customEvent)).toBeTruthy(); - // const eventDetails: IEventJson = (processor as any).subscribedEvents.get(customEvent).toJson(); - // expect(eventDetails.eventName).toEqual(customEvent); + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); - // expect(subSpy).toHaveBeenCalled(); + // Emit event and trigger callback + Emitter.emitZoweEvent(customEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - // EventOperator.deleteProcessor(appName); - // }); - // }); -}); \ No newline at end of file + expect(eventDetails.eventName).toEqual(customEvent); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(callback).toHaveBeenCalled(); + EventOperator.deleteProcessor("Zowe"); + }); + }); +}); From f52bed6ee35160553949ae321e6a7529c0e81c51 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 11:01:44 -0400 Subject: [PATCH 55/72] attempting to fix?? Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index c08422bda3..b924614e0e 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -17,6 +17,9 @@ import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; const appName = "Zowe"; const sampleApps = ["sample1", "sample2"]; +const userHome = require('os').homedir(); +const userEventsDir = path.join(userHome, '.zowe', '.events'); +let sharedEventsDir: string; let zoweCliHome: string; beforeAll(async () => { @@ -25,6 +28,7 @@ beforeAll(async () => { testName: "event_operator_and_processor" }); zoweCliHome = process.env.ZOWE_CLI_HOME || ''; + sharedEventsDir = path.join(zoweCliHome, '.events'); const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); ConfigUtils.writeExtendersJson(extJson); @@ -34,18 +38,16 @@ beforeEach(() => { jest.restoreAllMocks(); }); -afterEach(() => { - const sharedEventsDir = path.join(zoweCliHome, '.events'); +afterAll(() => { + if (fs.existsSync(userEventsDir)) { + fs.rmdirSync(userEventsDir, { recursive: true }); + } + if (fs.existsSync(sharedEventsDir)) { - fs.rmSync(sharedEventsDir, { recursive: true, force: true }); + fs.rmdirSync(sharedEventsDir, { recursive: true }); } }); -const doesEventFileExist = (eventDir: string, eventName: string) => { - const eventFilePath = path.join(eventDir, eventName); - return fs.existsSync(eventFilePath); -}; - describe("Event Operator and Processor", () => { const userEvent = ZoweUserEvents.ON_VAULT_CHANGED; const customEvent = "onCustomEvent"; @@ -55,7 +57,6 @@ describe("Event Operator and Processor", () => { it("should create an event file upon first subscription if file does not exist", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const callback = jest.fn(); - const eventDir = path.join(zoweCliHome, ".events"); const Watcher = EventOperator.getWatcher(appName); const Emitter = EventOperator.getZoweProcessor(); @@ -179,7 +180,7 @@ describe("Event Operator and Processor", () => { const callback = jest.fn(); const Watcher = EventOperator.getWatcher(sampleApps[0]); const Emitter = EventOperator.getZoweProcessor(); - const eventDir = path.join(zoweCliHome,sampleApps[0], ".events"); + const eventDir = path.join(zoweCliHome, sampleApps[0], ".events"); expect((Watcher as EventProcessor).subscribedEvents.get(customEvent)).toBeFalsy(); From d824817961aa28a0fa97d61e5a00123ff6de8d6a Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 11:05:02 -0400 Subject: [PATCH 56/72] more sensible but small organizational changes Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index b924614e0e..158881b921 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -49,9 +49,10 @@ afterAll(() => { }); describe("Event Operator and Processor", () => { - const userEvent = ZoweUserEvents.ON_VAULT_CHANGED; - const customEvent = "onCustomEvent"; const sharedEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const userEvent = ZoweUserEvents.ON_VAULT_CHANGED; + const customUserEvent = "onCustomUserEvent"; + const customSharedEvent = "onCustomSharedEvent"; describe("Zowe Events - Shared", () => { it("should create an event file upon first subscription if file does not exist", async () => { @@ -174,28 +175,56 @@ describe("Event Operator and Processor", () => { }); }); - describe("Custom Events", () => { - it("should create an event file upon first subscription if file does not exist - specific to custom event directory structure", async () => { + describe("Custom Events - Shared ", () => { + it("should create an event file upon first subscription if file does not exist - specific to CustomSharedEvent directory structure", async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const Watcher = EventOperator.getWatcher(sampleApps[0]); + const Emitter = EventOperator.getZoweProcessor(); + const eventDir = path.join(zoweCliHome, sampleApps[0], ".events"); + + expect((Watcher as EventProcessor).subscribedEvents.get(customSharedEvent)).toBeFalsy(); + + // Subscribe to event + Watcher.subscribeShared(customSharedEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customSharedEvent).toJson(); + + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); + + // Emit event and trigger callback + Emitter.emitZoweEvent(customSharedEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission + + expect(eventDetails.eventName).toEqual(customSharedEvent); + expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(callback).toHaveBeenCalled(); + EventOperator.deleteProcessor("Zowe"); + }); + }); + + describe("Custom Events - User ", () => { + it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const callback = jest.fn(); const Watcher = EventOperator.getWatcher(sampleApps[0]); const Emitter = EventOperator.getZoweProcessor(); const eventDir = path.join(zoweCliHome, sampleApps[0], ".events"); - expect((Watcher as EventProcessor).subscribedEvents.get(customEvent)).toBeFalsy(); + expect((Watcher as EventProcessor).subscribedEvents.get(customUserEvent)).toBeFalsy(); // Subscribe to event - Watcher.subscribeShared(customEvent, callback); - const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customEvent).toJson(); + Watcher.subscribeShared(customUserEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customUserEvent).toJson(); expect(callback).not.toHaveBeenCalled(); expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); // Emit event and trigger callback - Emitter.emitZoweEvent(customEvent); + Emitter.emitZoweEvent(customUserEvent); setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - expect(eventDetails.eventName).toEqual(customEvent); + expect(eventDetails.eventName).toEqual(customUserEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(callback).toHaveBeenCalled(); EventOperator.deleteProcessor("Zowe"); From 8723259a5fb31127153a336561b5919b51b1ed34 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 11:44:03 -0400 Subject: [PATCH 57/72] race condition fix Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/EventUtils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 2a4ca377b6..f93107574c 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -123,16 +123,13 @@ export class EventUtils { */ public static ensureFileExists(filePath: string) { try { - if (!fs.existsSync(filePath)) { - // eslint-disable-next-line @typescript-eslint/no-magic-numbers - fs.closeSync(fs.openSync(filePath, 'w+', 0o640)); // user read/write, group read - } + const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o600); + fs.closeSync(fd); } catch (err) { throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); } } - /** * Create an event with minimal information * From 93f73c1a776e4ce2906b4292a9958def0f503da8 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 15:25:32 -0400 Subject: [PATCH 58/72] fixing integration tests and ammending race condition fix Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 217 +++++++----------- .../imperative/src/events/src/EventUtils.ts | 6 +- 2 files changed, 83 insertions(+), 140 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 158881b921..1ae6ec470d 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -1,11 +1,11 @@ /* -* This program and accompanying materials are made available under terms of +* 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 Zowe Project. +* Copyright Contributors to the Zowe Project. * */ @@ -15,50 +15,43 @@ import * as fs from "fs"; import * as path from "path"; import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; -const appName = "Zowe"; -const sampleApps = ["sample1", "sample2"]; -const userHome = require('os').homedir(); -const userEventsDir = path.join(userHome, '.zowe', '.events'); -let sharedEventsDir: string; +const zoweApp = "Zowe"; +const sampleApps = ["firstSample", "secondSample"]; +let testsEventDir: string; let zoweCliHome: string; -beforeAll(async () => { - await SetupTestEnvironment.createTestEnv({ - cliHomeEnvVar: "ZOWE_CLI_HOME", - testName: "event_operator_and_processor" - }); - zoweCliHome = process.env.ZOWE_CLI_HOME || ''; - sharedEventsDir = path.join(zoweCliHome, '.events'); - const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); - sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); - ConfigUtils.writeExtendersJson(extJson); -}); - -beforeEach(() => { - jest.restoreAllMocks(); -}); - -afterAll(() => { - if (fs.existsSync(userEventsDir)) { - fs.rmdirSync(userEventsDir, { recursive: true }); - } - - if (fs.existsSync(sharedEventsDir)) { - fs.rmdirSync(sharedEventsDir, { recursive: true }); - } -}); - describe("Event Operator and Processor", () => { const sharedEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; - const userEvent = ZoweUserEvents.ON_VAULT_CHANGED; const customUserEvent = "onCustomUserEvent"; - const customSharedEvent = "onCustomSharedEvent"; + beforeAll(async () => { + await SetupTestEnvironment.createTestEnv({ + cliHomeEnvVar: "ZOWE_CLI_HOME", + testName: "event_operator_and_processor" + }); + // have to reset because test environment doesn't add .zowe to ZOWE_CLI_HOME :( + process.env.ZOWE_CLI_HOME = path.join(process.env.ZOWE_CLI_HOME || '', ".zowe"); + zoweCliHome = process.env.ZOWE_CLI_HOME; + EventUtils.ensureEventsDirExists(zoweCliHome); + testsEventDir = path.join(zoweCliHome, '.events'); + const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); + sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); + ConfigUtils.writeExtendersJson(extJson); + }); + + beforeEach(() => { + jest.restoreAllMocks(); + }); - describe("Zowe Events - Shared", () => { + // afterAll(() => { + // if (fs.existsSync(testsEventDir)) { + // fs.rmdirSync(testsEventDir, { recursive: true }); + // } +// }); + describe("Zowe Events", () => { it("should create an event file upon first subscription if file does not exist", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const callback = jest.fn(); - const Watcher = EventOperator.getWatcher(appName); + const Watcher = EventOperator.getWatcher(zoweApp); const Emitter = EventOperator.getZoweProcessor(); expect((Watcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); @@ -72,162 +65,110 @@ describe("Event Operator and Processor", () => { // Emit event and trigger callback Emitter.emitZoweEvent(sharedEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission expect(eventDetails.eventName).toEqual(sharedEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(callback).toHaveBeenCalled(); - EventOperator.deleteProcessor(appName); + EventOperator.deleteProcessor(zoweApp); }); it("should trigger subscriptions for all instances watching for ZoweSharedEvent", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const firstWatcher = EventOperator.getWatcher(sampleApps[0]); - const secondWatcher = EventOperator.getWatcher(sampleApps[1]); + // create two watchers watching the same app (Zowe) + const firstWatcher = EventOperator.getWatcher(); + const secondWatcher = EventOperator.getWatcher(); const Emitter = EventOperator.getZoweProcessor(); - const FirstCallback: EventCallback = jest.fn() as EventCallback; - const SecondCallback: EventCallback = jest.fn() as EventCallback; + const firstCallback: EventCallback = jest.fn() as EventCallback; + const secondCallback: EventCallback = jest.fn() as EventCallback; expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); // Subscribe to event - firstWatcher.subscribeShared(sharedEvent, FirstCallback); - secondWatcher.subscribeShared(sharedEvent, SecondCallback); + firstWatcher.subscribeShared(sharedEvent, firstCallback); + secondWatcher.subscribeShared(sharedEvent, secondCallback); const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedEvent).toJson(); const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedEvent).toJson(); // Emit event and trigger callbacks Emitter.emitZoweEvent(sharedEvent); - - // Adding a delay to ensure callbacks have time to be called - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); expect(firstEventDetails.eventName).toEqual(sharedEvent); expect(secondEventDetails.eventName).toEqual(sharedEvent); expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); - expect(FirstCallback).toHaveBeenCalled(); - expect(SecondCallback).toHaveBeenCalled(); - EventOperator.deleteProcessor(sampleApps[0]); - EventOperator.deleteProcessor(sampleApps[1]); - EventOperator.deleteProcessor(appName); + expect(firstCallback).toHaveBeenCalled(); + expect(secondCallback).toHaveBeenCalled(); + EventOperator.deleteProcessor(zoweApp); }); it("should not affect subscriptions from another instance when unsubscribing from events", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const firstWatcher = EventOperator.getWatcher(sampleApps[0]); - const secondProc = EventOperator.getZoweProcessor(); + // create two watchers watching the same app (Zowe) + // BUT because we are in the same process and can't actually simulate different processes, + // need to fake out unsubscription of secondWatcher by watching for the same event on another app + const firstWatcher = EventOperator.getWatcher(); + const secondWatcher = EventOperator.getWatcher(sampleApps[0]); + const Emitter = EventOperator.getZoweProcessor(); - const firstSubSpy = jest.fn(); - const secondSubSpy = jest.fn(); + const firstCallback: EventCallback = jest.fn() as EventCallback; + const secondCallback: EventCallback = jest.fn() as EventCallback; - firstWatcher.subscribeShared(sharedEvent, firstSubSpy); - secondProc.subscribeShared(sharedEvent, secondSubSpy); + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + + // Subscribe to event + firstWatcher.subscribeShared(sharedEvent, firstCallback); + secondWatcher.subscribeShared(sharedEvent, secondCallback); + const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedEvent).toJson(); + const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedEvent).toJson(); - firstWatcher.unsubscribe(sharedEvent); + secondWatcher.unsubscribe(sharedEvent); - expect((firstWatcher as any).subscribedEvents.get(sharedEvent)).toBeFalsy(); - expect((secondProc as any).subscribedEvents.get(sharedEvent)).toBeTruthy(); + expect((firstWatcher as any).subscribedEvents.get(sharedEvent)).toBeTruthy(); + expect((secondWatcher as any).subscribedEvents.get(sharedEvent)).toBeFalsy(); // Emit event and trigger callbacks - secondProc.emitZoweEvent(sharedEvent); - - // Adding a delay to ensure callbacks have time to be called + Emitter.emitZoweEvent(sharedEvent); setupWatcherSpy.mock.calls.forEach(call => { - if (call[0].appName === appName) { (call[2] as Function)(); } - }); // Mock event emission - - expect(firstSubSpy).not.toHaveBeenCalled(); - expect(secondSubSpy).toHaveBeenCalled(); + if (call[0].appName === zoweApp) { (call[2] as Function)(); } + }); + expect(firstCallback).toHaveBeenCalled(); + expect(secondCallback).not.toHaveBeenCalled(); EventOperator.deleteProcessor(sampleApps[0]); - EventOperator.deleteProcessor(appName); - }); - }); - - describe("Zowe Events - User", () => { - it("should create an event file upon first subscription if file does not exist - specific to user event directory structure", async () => { - const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const callback = jest.fn(); - const eventDir = path.join(zoweCliHome, ".events"); - const Watcher = EventOperator.getWatcher(sampleApps[0]); - const Emitter = EventOperator.getZoweProcessor(); - - expect((Watcher as EventProcessor).subscribedEvents.get(userEvent)).toBeFalsy(); - - // Subscribe to event - Watcher.subscribeShared(userEvent, callback); - const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(userEvent).toJson(); - - expect(callback).not.toHaveBeenCalled(); - expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); - - // Emit event and trigger callback - Emitter.emitZoweEvent(userEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - - expect(eventDetails.eventName).toEqual(userEvent); - expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); - expect(callback).toHaveBeenCalled(); - EventOperator.deleteProcessor(appName); - }); - }); - - describe("Custom Events - Shared ", () => { - it("should create an event file upon first subscription if file does not exist - specific to CustomSharedEvent directory structure", async () => { - const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const callback = jest.fn(); - const Watcher = EventOperator.getWatcher(sampleApps[0]); - const Emitter = EventOperator.getZoweProcessor(); - const eventDir = path.join(zoweCliHome, sampleApps[0], ".events"); - - expect((Watcher as EventProcessor).subscribedEvents.get(customSharedEvent)).toBeFalsy(); - - // Subscribe to event - Watcher.subscribeShared(customSharedEvent, callback); - const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customSharedEvent).toJson(); - - expect(callback).not.toHaveBeenCalled(); - expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); - - // Emit event and trigger callback - Emitter.emitZoweEvent(customSharedEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - - expect(eventDetails.eventName).toEqual(customSharedEvent); - expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); - expect(callback).toHaveBeenCalled(); - EventOperator.deleteProcessor("Zowe"); + EventOperator.deleteProcessor(zoweApp); }); }); - describe("Custom Events - User ", () => { + describe("Custom Events ", () => { it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const callback = jest.fn(); - const Watcher = EventOperator.getWatcher(sampleApps[0]); - const Emitter = EventOperator.getZoweProcessor(); - const eventDir = path.join(zoweCliHome, sampleApps[0], ".events"); + const Watcher = EventOperator.getWatcher(sampleApps[1]); + const Emitter = EventOperator.getEmitter(sampleApps[1]); + const eventDir = path.join(zoweCliHome, ".events", sampleApps[1]); //corresponds to emitter's event file expect((Watcher as EventProcessor).subscribedEvents.get(customUserEvent)).toBeFalsy(); - // Subscribe to event - Watcher.subscribeShared(customUserEvent, callback); + // Subscribe to event + Watcher.subscribeUser(customUserEvent, callback); const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customUserEvent).toJson(); - expect(callback).not.toHaveBeenCalled(); expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); // Emit event and trigger callback - Emitter.emitZoweEvent(customUserEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission + Emitter.emitEvent(customUserEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); expect(eventDetails.eventName).toEqual(customUserEvent); - expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); + expect(eventDetails.eventFilePath).toContain(eventDir); expect(callback).toHaveBeenCalled(); - EventOperator.deleteProcessor("Zowe"); + expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeFalsy(); //ensuring this custom event isnt a Zowe event + EventOperator.deleteProcessor(sampleApps[1]); }); }); }); diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index f93107574c..29f9ca311c 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -123,10 +123,12 @@ export class EventUtils { */ public static ensureFileExists(filePath: string) { try { - const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o600); + const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o640); fs.closeSync(fd); } catch (err) { - throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); + if (err.code!=='EEXIST'){ + throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); + } } } From 637a2d222002e169007de73c59693a60a26be6a4 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 15:32:46 -0400 Subject: [PATCH 59/72] SPACE :alien: Signed-off-by: Amber Torrise --- .../EventOperator_and_Processor.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 1ae6ec470d..085886b5d2 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -144,7 +144,7 @@ describe("Event Operator and Processor", () => { }); }); - describe("Custom Events ", () => { + describe("Custom Events", () => { it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); const callback = jest.fn(); From f36025d811610a807eefa659012a0321b90d02b0 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 15:36:09 -0400 Subject: [PATCH 60/72] :tada: Signed-off-by: Amber Torrise --- .../EventOperator_and_Processor.integration.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 085886b5d2..6efec48b62 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -10,7 +10,7 @@ */ import { SetupTestEnvironment } from "../../../../__tests__/__src__/environment/SetupTestEnvironment"; -import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents, ZoweUserEvents } from "../../.."; +import { ConfigUtils, EventCallback, EventOperator, EventProcessor, EventUtils, IEventJson, ZoweSharedEvents } from "../../.."; import * as fs from "fs"; import * as path from "path"; import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; @@ -42,11 +42,6 @@ describe("Event Operator and Processor", () => { jest.restoreAllMocks(); }); - // afterAll(() => { - // if (fs.existsSync(testsEventDir)) { - // fs.rmdirSync(testsEventDir, { recursive: true }); - // } -// }); describe("Zowe Events", () => { it("should create an event file upon first subscription if file does not exist", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); @@ -123,9 +118,8 @@ describe("Event Operator and Processor", () => { // Subscribe to event firstWatcher.subscribeShared(sharedEvent, firstCallback); secondWatcher.subscribeShared(sharedEvent, secondCallback); - const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedEvent).toJson(); - const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedEvent).toJson(); + // unsubscribe! secondWatcher.unsubscribe(sharedEvent); expect((firstWatcher as any).subscribedEvents.get(sharedEvent)).toBeTruthy(); From cb910196273ee70b7e51ddb33d104dafdf678199 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 15:51:15 -0400 Subject: [PATCH 61/72] changes for clarity Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 6efec48b62..6ad94b891c 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -17,11 +17,14 @@ import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; const zoweApp = "Zowe"; const sampleApps = ["firstSample", "secondSample"]; -let testsEventDir: string; let zoweCliHome: string; +/** + * | Zowe Event Dir | <...>/.zowe/.events/Zowe/ + * | Custom Event Dir | <...>/.zowe/.events/custApp/ + */ describe("Event Operator and Processor", () => { - const sharedEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; + const sharedZoweEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; const customUserEvent = "onCustomUserEvent"; beforeAll(async () => { await SetupTestEnvironment.createTestEnv({ @@ -32,7 +35,6 @@ describe("Event Operator and Processor", () => { process.env.ZOWE_CLI_HOME = path.join(process.env.ZOWE_CLI_HOME || '', ".zowe"); zoweCliHome = process.env.ZOWE_CLI_HOME; EventUtils.ensureEventsDirExists(zoweCliHome); - testsEventDir = path.join(zoweCliHome, '.events'); const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); ConfigUtils.writeExtendersJson(extJson); @@ -49,20 +51,20 @@ describe("Event Operator and Processor", () => { const Watcher = EventOperator.getWatcher(zoweApp); const Emitter = EventOperator.getZoweProcessor(); - expect((Watcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((Watcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); // Subscribe to event - Watcher.subscribeShared(sharedEvent, callback); - const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(sharedEvent).toJson(); + Watcher.subscribeShared(sharedZoweEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); expect(callback).not.toHaveBeenCalled(); expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); // Emit event and trigger callback - Emitter.emitZoweEvent(sharedEvent); + Emitter.emitZoweEvent(sharedZoweEvent); setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); // Mock event emission - expect(eventDetails.eventName).toEqual(sharedEvent); + expect(eventDetails.eventName).toEqual(sharedZoweEvent); expect(EventUtils.isSharedEvent(eventDetails.eventName)).toBeTruthy(); expect(callback).toHaveBeenCalled(); EventOperator.deleteProcessor(zoweApp); @@ -70,7 +72,7 @@ describe("Event Operator and Processor", () => { it("should trigger subscriptions for all instances watching for ZoweSharedEvent", async () => { const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - // create two watchers watching the same app (Zowe) + // create two watchers watching the same app (Zowe - default) const firstWatcher = EventOperator.getWatcher(); const secondWatcher = EventOperator.getWatcher(); const Emitter = EventOperator.getZoweProcessor(); @@ -78,21 +80,22 @@ describe("Event Operator and Processor", () => { const firstCallback: EventCallback = jest.fn() as EventCallback; const secondCallback: EventCallback = jest.fn() as EventCallback; - expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); - expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + // We expect no subscriptions yet + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); // Subscribe to event - firstWatcher.subscribeShared(sharedEvent, firstCallback); - secondWatcher.subscribeShared(sharedEvent, secondCallback); - const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedEvent).toJson(); - const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedEvent).toJson(); + firstWatcher.subscribeShared(sharedZoweEvent, firstCallback); + secondWatcher.subscribeShared(sharedZoweEvent, secondCallback); + const firstEventDetails: IEventJson = (firstWatcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); + const secondEventDetails: IEventJson = (secondWatcher as any).subscribedEvents.get(sharedZoweEvent).toJson(); // Emit event and trigger callbacks - Emitter.emitZoweEvent(sharedEvent); + Emitter.emitZoweEvent(sharedZoweEvent); setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); - expect(firstEventDetails.eventName).toEqual(sharedEvent); - expect(secondEventDetails.eventName).toEqual(sharedEvent); + expect(firstEventDetails.eventName).toEqual(sharedZoweEvent); + expect(secondEventDetails.eventName).toEqual(sharedZoweEvent); expect(EventUtils.isSharedEvent(firstEventDetails.eventName)).toBeTruthy(); expect(EventUtils.isSharedEvent(secondEventDetails.eventName)).toBeTruthy(); expect(firstCallback).toHaveBeenCalled(); @@ -112,21 +115,21 @@ describe("Event Operator and Processor", () => { const firstCallback: EventCallback = jest.fn() as EventCallback; const secondCallback: EventCallback = jest.fn() as EventCallback; - expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); - expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((firstWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); + expect((secondWatcher as EventProcessor).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); // Subscribe to event - firstWatcher.subscribeShared(sharedEvent, firstCallback); - secondWatcher.subscribeShared(sharedEvent, secondCallback); + firstWatcher.subscribeShared(sharedZoweEvent, firstCallback); + secondWatcher.subscribeShared(sharedZoweEvent, secondCallback); // unsubscribe! - secondWatcher.unsubscribe(sharedEvent); + secondWatcher.unsubscribe(sharedZoweEvent); - expect((firstWatcher as any).subscribedEvents.get(sharedEvent)).toBeTruthy(); - expect((secondWatcher as any).subscribedEvents.get(sharedEvent)).toBeFalsy(); + expect((firstWatcher as any).subscribedEvents.get(sharedZoweEvent)).toBeTruthy(); + expect((secondWatcher as any).subscribedEvents.get(sharedZoweEvent)).toBeFalsy(); // Emit event and trigger callbacks - Emitter.emitZoweEvent(sharedEvent); + Emitter.emitZoweEvent(sharedZoweEvent); setupWatcherSpy.mock.calls.forEach(call => { if (call[0].appName === zoweApp) { (call[2] as Function)(); } }); From 06ef81002c675a8c7efe8f04c56e67e9d811f26c Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 15:52:22 -0400 Subject: [PATCH 62/72] changes for clarity Signed-off-by: Amber Torrise --- .../EventOperator_and_Processor.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 6ad94b891c..577697b856 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -16,7 +16,7 @@ import * as path from "path"; import { IExtendersJsonOpts } from "../../../config/src/doc/IExtenderOpts"; const zoweApp = "Zowe"; -const sampleApps = ["firstSample", "secondSample"]; +const sampleApps = ["firstApp", "secondApp"]; let zoweCliHome: string; /** From 5301fa5ae3c0b7d04c4ad622ea71d6daa91f13d3 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Thu, 27 Jun 2024 16:15:57 -0400 Subject: [PATCH 63/72] creating an explanation in the type doc for EventOperator Signed-off-by: Amber Torrise --- .../src/events/src/EventOperator.ts | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 1f16b52bbf..30ffbc22d8 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -21,15 +21,42 @@ interface IZoweProcessor extends IEmitterAndWatcher { } /** - * Manages event processors for different applications, facilitating subscription, - * emission, and watching of events. + * ## Overview + * The `EventOperator` is the central point for managing event processors within an application. + * It allows different parts of an application to subscribe to, emit, and watch events in a structured manner. + * + * An `EventOperator` manages three types of event processors: + * - **Watcher**: Listens for events and triggers callbacks when events occur. + * - **Emitter**: Emits events that other applications listen for. + * - **EmitterAndWatcher**: Combines the functionalities of both watcher and emitter. + * + * Applications use the `EventOperator` to obtain the appropriate event processor based on their needs. + * For example, an application might use a watcher to react to user actions and an emitter to notify other + * components of state changes. + * + * ### Application Use Cases + * - **Getting a Processor for Emitting**: Use this when your application needs to emit events. + * For example, a data service might emit events whenever data is updated. + * - **Getting a Processor for Watching**: Use this when your application needs to react to events. + * For example, a UI component might watch for data update events to refresh its display. + * - **Managing Event Subscriptions**: Applications can subscribe to predefined Zowe events or define + * custom events. This flexibility allows applications to integrate with the Zowe ecosystem or + * create their own event-driven functionality. + * + * ### App Names and Processors + * Processors are tied to application names to prevent event collisions and to maintain a clear separation + * of event domains. Valid app names are defined in the list of extenders (formal plugin names or ZE extender names). + * + * ### Predefined and Custom Events + * - **Predefined Zowe Events**: Zowe provides a set of predefined events that can be watched. + * These events are well-defined and documented within the Zowe ecosystem. + * - **Custom Events**: Applications can define their own events, allowing for custom event-driven behavior. * * @export * @class EventOperator */ export class EventOperator { private static instances: Map = new Map(); - /** * Creates an event processor for a specified application. * @@ -51,7 +78,6 @@ export class EventOperator { } return procInstance; } - /** * Retrieves a Zowe-specific event processor. The purpose of this method is for internal * Imperative APIs to get a properly initialized processor. This processor will be used From 7403112895be7e9d8e7454493d96337040d4c93c Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:03:36 -0400 Subject: [PATCH 64/72] fix: make sure the env prefix is based on the app name Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../config/__tests__/ProfileInfo.TeamConfig.unit.test.ts | 8 +++----- packages/imperative/src/config/src/ProfileInfo.ts | 7 +++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index cdc9cad898..0f62894524 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -480,6 +480,9 @@ describe("TeamConfig ProfileInfo tests", () => { }); describe("mergeArgsForProfile", () => { + beforeEach(() => { + (ImperativeConfig as any).mInstance = null; + }); afterEach(() => { delete process.env[envHost]; delete process.env[envPort]; @@ -860,11 +863,6 @@ describe("TeamConfig ProfileInfo tests", () => { process.env[testEnvPrefix + "_CLI_HOME"] = nestedTeamProjDir; await profInfo.readProfilesFromDisk(); const profiles = profInfo.getAllProfiles(); - // expect(ImperativeConfig.instance.loadedConfig).toBeUndefined(); - - // TODO(zFernand0): investigate why global layer profiles are not loaded - expect(profiles).toEqual([]); // This should prove the above statement - const desiredProfile = "TEST001.first"; const profAttrs = profiles.find(p => p.profName === desiredProfile); let mergedArgs; diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 94321ae6bd..58b101b400 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1599,9 +1599,12 @@ export class ProfileInfo { */ private overrideWithEnv(mergedArgs: IProfMergedArg, profSchema?: IProfileSchema) { if (!this.mOverrideWithEnv) return; // Don't do anything - + + // const envPrefix = ImperativeConfig.instance.envVariablePrefix; + const envPrefix = this.mAppName.toUpperCase(); + // Do we expect to always read "ZOWE_OPT_" environmental variables or "APPNAME_OPT_"? + // Populate any missing options - const envPrefix = ImperativeConfig.instance.loadedConfig.envVariablePrefix; const envStart = envPrefix + "_OPT_"; for (const key in process.env) { if (key.startsWith(envStart)) { From bd8b9228c3653847aff11e2d6ac2ee8720aae236 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 1 Jul 2024 10:18:16 -0400 Subject: [PATCH 65/72] modifying descriptions Signed-off-by: Amber Torrise --- ...Operator_and_Processor.integration.test.ts | 8 +++++- .../src/events/src/EventOperator.ts | 16 +++--------- .../src/events/src/EventProcessor.ts | 26 +++++++++++++------ 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 577697b856..2d53d7f47a 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -20,9 +20,15 @@ const sampleApps = ["firstApp", "secondApp"]; let zoweCliHome: string; /** + * ## Understanding Event Files * | Zowe Event Dir | <...>/.zowe/.events/Zowe/ * | Custom Event Dir | <...>/.zowe/.events/custApp/ - */ + * + * ## Understanding Event Types + * - **Shared Events**: Zowe events that when triggered, notify all subscribed users. + * - **User Events**: Zowe events that are specific to a particular user or session. + * - **Custom Events**: Applications can define their own shared and user events. +*/ describe("Event Operator and Processor", () => { const sharedZoweEvent = ZoweSharedEvents.ON_CREDENTIAL_MANAGER_CHANGED; const customUserEvent = "onCustomUserEvent"; diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 30ffbc22d8..a3ddc9279e 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -22,10 +22,9 @@ interface IZoweProcessor extends IEmitterAndWatcher { /** * ## Overview - * The `EventOperator` is the central point for managing event processors within an application. - * It allows different parts of an application to subscribe to, emit, and watch events in a structured manner. + * The `EventOperator` manages event processors and sets limits on their behavior. * - * An `EventOperator` manages three types of event processors: + * An `EventOperator` categorizes processors into 3 types: * - **Watcher**: Listens for events and triggers callbacks when events occur. * - **Emitter**: Emits events that other applications listen for. * - **EmitterAndWatcher**: Combines the functionalities of both watcher and emitter. @@ -34,21 +33,12 @@ interface IZoweProcessor extends IEmitterAndWatcher { * For example, an application might use a watcher to react to user actions and an emitter to notify other * components of state changes. * - * ### Application Use Cases - * - **Getting a Processor for Emitting**: Use this when your application needs to emit events. - * For example, a data service might emit events whenever data is updated. - * - **Getting a Processor for Watching**: Use this when your application needs to react to events. - * For example, a UI component might watch for data update events to refresh its display. - * - **Managing Event Subscriptions**: Applications can subscribe to predefined Zowe events or define - * custom events. This flexibility allows applications to integrate with the Zowe ecosystem or - * create their own event-driven functionality. - * * ### App Names and Processors * Processors are tied to application names to prevent event collisions and to maintain a clear separation * of event domains. Valid app names are defined in the list of extenders (formal plugin names or ZE extender names). * * ### Predefined and Custom Events - * - **Predefined Zowe Events**: Zowe provides a set of predefined events that can be watched. + * - **Predefined Zowe Events**: Zowe provides a set of predefined events that can be watched. * These events are well-defined and documented within the Zowe ecosystem. * - **Custom Events**: Applications can define their own events, allowing for custom event-driven behavior. * diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index f277a5f357..346cd8d5bc 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -21,8 +21,18 @@ import { IEventDisposable } from "./doc"; import { IProcessorTypes } from "./doc/IEventInstanceTypes"; /** - * Manages event subscriptions and emissions for a specific application. + * ## Overview + * The `EventProcessor` class manages event subscriptions and emissions for a specific application. * + * An `EventProcessor` handles three main functionalities: + * - **Subscribing to Events**: Registration of a callback function that will be executed when that event occurs. + * - **Emitting Events**: Notifying other applications or parts of the same application about certain actions or changes. + * - **Managing Event Subscriptions**: Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. + * + * ### Understanding Event Types + * - **Shared Events**: Zowe events that when triggered, notify all subscribed users. + * - **User Events**: Zowe events that are specific to only one user. + * - **Custom Events**: Applications can define their own shared and user events. * @export * @class EventProcessor */ @@ -48,7 +58,7 @@ export class EventProcessor { this.appName = appName; this.processorType = type; - // Ensure we have correct environmental conditions to setup a custom logger, + // Ensure correct environmental conditions to setup a custom logger, // otherwise use default logger if (ImperativeConfig.instance.loadedConfig == null || LoggerManager.instance.isLoggerInit === false) { ConfigUtils.initImpUtils("zowe"); @@ -57,12 +67,12 @@ export class EventProcessor { } /** - * Subscribes to shared events by creating and managing a new subscription. + * Subscription to an event that will notify all subscribed users. * * @param {string} eventName - The name of the event to subscribe to. * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. - * @returns {IEventDisposable} - Object allowing management of the subscription. - */ + * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. + */ public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); @@ -75,11 +85,11 @@ export class EventProcessor { } /** - * Subscribes to user-specific events by creating and managing a new subscription. - * + * Subscription to an event that will notify a single user. + * * @param {string} eventName - The name of the event to subscribe to. * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. - * @returns {IEventDisposable} - Object allowing management of the subscription. + * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. */ public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { if (this.processorType === IProcessorTypes.EMITTER) { From aa7d98036f3521368cd2a0ca1a92bcc000a2d760 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 1 Jul 2024 10:33:18 -0400 Subject: [PATCH 66/72] fixes to descriptions and type doc Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/EventOperator.ts | 7 +++---- packages/imperative/src/events/src/EventProcessor.ts | 8 +++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index a3ddc9279e..65642315bb 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -37,9 +37,8 @@ interface IZoweProcessor extends IEmitterAndWatcher { * Processors are tied to application names to prevent event collisions and to maintain a clear separation * of event domains. Valid app names are defined in the list of extenders (formal plugin names or ZE extender names). * - * ### Predefined and Custom Events - * - **Predefined Zowe Events**: Zowe provides a set of predefined events that can be watched. - * These events are well-defined and documented within the Zowe ecosystem. + * ### Understanding Event Types + * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. * - **Custom Events**: Applications can define their own events, allowing for custom event-driven behavior. * * @export @@ -72,7 +71,7 @@ export class EventOperator { * Retrieves a Zowe-specific event processor. The purpose of this method is for internal * Imperative APIs to get a properly initialized processor. This processor will be used * when applications (like Zowe Explorer) call Imperative APIs that trigger events. For - * example, when the user updates credentials from Zowe Explorer, this processor will be + * example, when the user updates credentials from Zowe Explorer, this processor could be * used to emit an `OnVaultChanged` event. * * @internal Not meant to be called by application developers diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 346cd8d5bc..ae5dae24d3 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -22,7 +22,8 @@ import { IProcessorTypes } from "./doc/IEventInstanceTypes"; /** * ## Overview - * The `EventProcessor` class manages event subscriptions and emissions for a specific application. + * Each EventProcessor manages its own subscriptions, handling the addition, emission, and removal of events. + * It uses a map where event names are keys, and values are Event objects that hold detailed event information and subscriptions. * * An `EventProcessor` handles three main functionalities: * - **Subscribing to Events**: Registration of a callback function that will be executed when that event occurs. @@ -30,9 +31,10 @@ import { IProcessorTypes } from "./doc/IEventInstanceTypes"; * - **Managing Event Subscriptions**: Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. * * ### Understanding Event Types - * - **Shared Events**: Zowe events that when triggered, notify all subscribed users. - * - **User Events**: Zowe events that are specific to only one user. + * - **Zowe User Events**: Zowe events that when triggered, notify all subscribed users. + * - **Zowe Shared Events**: Zowe events that are specific to only one user. * - **Custom Events**: Applications can define their own shared and user events. + * * @export * @class EventProcessor */ From 44788acc585807c9cfcab3d4de4ba78d7f2d3a00 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 1 Jul 2024 10:36:47 -0400 Subject: [PATCH 67/72] mild change to documentation Signed-off-by: Amber Torrise --- packages/imperative/src/events/src/EventProcessor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index ae5dae24d3..8bfc5b3d3a 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -31,9 +31,9 @@ import { IProcessorTypes } from "./doc/IEventInstanceTypes"; * - **Managing Event Subscriptions**: Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. * * ### Understanding Event Types - * - **Zowe User Events**: Zowe events that when triggered, notify all subscribed users. - * - **Zowe Shared Events**: Zowe events that are specific to only one user. - * - **Custom Events**: Applications can define their own shared and user events. + * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. + * - **Custom Events**: Applications can define their own shared and user events, allowing for custom event-driven behavior. + * * * @export * @class EventProcessor From dd29a965e29d956c829e0507a70e18f015f13590 Mon Sep 17 00:00:00 2001 From: Amber Torrise Date: Mon, 1 Jul 2024 11:20:59 -0400 Subject: [PATCH 68/72] addressing pt 1 of trae's comments: tidying up Signed-off-by: Amber Torrise --- .../src/config/__tests__/Config.secure.unit.test.ts | 2 -- packages/imperative/src/config/src/ProfileInfo.ts | 7 +++---- packages/imperative/src/events/src/EventOperator.ts | 2 +- packages/imperative/src/events/src/EventProcessor.ts | 1 - .../__tests__/config/cmd/init/init.handler.unit.test.ts | 1 - .../config/cmd/secure/secure.handler.unit.test.ts | 1 - .../__tests__/config/cmd/set/set.handler.unit.test.ts | 3 --- .../__tests__/.events/Zowe/onCredentialManagerChanged | 7 ------- 8 files changed, 4 insertions(+), 20 deletions(-) delete mode 100644 packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged diff --git a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts index d7188a514a..72b632c70a 100644 --- a/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts +++ b/packages/imperative/src/config/__tests__/Config.secure.unit.test.ts @@ -46,8 +46,6 @@ describe("Config secure tests", () => { }); beforeEach(() => { - // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); - mockSecureLoad = jest.fn(); mockSecureSave = jest.fn(); mockVault = { diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 58b101b400..1230df3706 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -1586,7 +1586,7 @@ export class ProfileInfo { // _______________________________________________________________________ /** * Override values in a merged argument object with values found in - * environment variables. The choice to override enviroment variables is + * environment variables. The choice to override environment variables is * controlled by an option on the ProfileInfo constructor. * * @param mergedArgs @@ -1599,11 +1599,10 @@ export class ProfileInfo { */ private overrideWithEnv(mergedArgs: IProfMergedArg, profSchema?: IProfileSchema) { if (!this.mOverrideWithEnv) return; // Don't do anything - - // const envPrefix = ImperativeConfig.instance.envVariablePrefix; + const envPrefix = this.mAppName.toUpperCase(); // Do we expect to always read "ZOWE_OPT_" environmental variables or "APPNAME_OPT_"? - + // Populate any missing options const envStart = envPrefix + "_OPT_"; for (const key in process.env) { diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 65642315bb..1b0261f2bc 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -39,7 +39,7 @@ interface IZoweProcessor extends IEmitterAndWatcher { * * ### Understanding Event Types * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. - * - **Custom Events**: Applications can define their own events, allowing for custom event-driven behavior. + * - **Custom Events**: Applications can define their own shared/user events, allowing for custom event-driven behavior. * * @export * @class EventOperator diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 8bfc5b3d3a..55c623d472 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -34,7 +34,6 @@ import { IProcessorTypes } from "./doc/IEventInstanceTypes"; * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. * - **Custom Events**: Applications can define their own shared and user events, allowing for custom event-driven behavior. * - * * @export * @class EventProcessor */ diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts index 92d1af3a30..30cdada61e 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/init/init.handler.unit.test.ts @@ -27,7 +27,6 @@ import { OverridesLoader } from "../../../../src/OverridesLoader"; import { ConfigUtils, ImperativeError } from "../../../../.."; jest.mock("fs"); -// jest.mock("../../../../../events/src/ImperativeEventEmitter"); const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts index 4299852f0a..a2926aec91 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/secure/secure.handler.unit.test.ts @@ -103,7 +103,6 @@ describe("Configuration Secure command handler", () => { }; beforeAll( async() => { - // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); keytarGetPasswordSpy = jest.spyOn(keytar, "getPassword"); keytarSetPasswordSpy = jest.spyOn(keytar, "setPassword"); keytarDeletePasswordSpy = jest.spyOn(keytar, "deletePassword"); diff --git a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts index 6f6521d822..272c140435 100644 --- a/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts +++ b/packages/imperative/src/imperative/__tests__/config/cmd/set/set.handler.unit.test.ts @@ -27,9 +27,6 @@ import * as fs from "fs"; import { setupConfigToLoad } from "../../../../../../__tests__/src/TestUtil"; import { EventOperator, EventUtils } from "../../../../../events"; -// jest.mock("../../../../../events/src/ImperativeEventEmitter"); - - const getIHandlerParametersObject = (): IHandlerParameters => { const x: any = { response: { diff --git a/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged b/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged deleted file mode 100644 index 801a37dc93..0000000000 --- a/packages/imperative/src/security/__tests__/.events/Zowe/onCredentialManagerChanged +++ /dev/null @@ -1,7 +0,0 @@ -{ - "eventTime": "2024-06-10T18:29:35.048Z", - "eventName": "onCredentialManagerChanged", - "eventType": null, - "appName": "Zowe", - "eventFilePath": "C:\\Users\\at895452\\Desktop\\zowe-cli-v3\\zowe-cli\\packages\\imperative\\src\\security\\__tests__\\.events\\Zowe\\onCredentialManagerChanged" -} \ No newline at end of file From a4e268a1aa59e8e63e04129b099d3cae44119e5d Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:09:01 -0400 Subject: [PATCH 69/72] chore: addressed PR comments and a few other things Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- .../imperative/src/config/src/ConfigUtils.ts | 2 + .../src/events/src/EventOperator.ts | 4 ++ .../src/events/src/EventProcessor.ts | 51 +++++++++++-------- .../imperative/src/events/src/EventUtils.ts | 19 ++++++- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 3e5acbd642..22767fcd5e 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -46,6 +46,8 @@ export class ConfigUtils { * Reads the `extenders.json` file from the CLI home directory. * Called once in `readProfilesFromDisk` and cached to minimize I/O operations. * @internal + * @throws If the extenders.json file cannot be created when it does not exist. + * @throws If the extenders.json file cannot be read. */ public static readExtendersJson(): IExtendersJsonOpts { const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); diff --git a/packages/imperative/src/events/src/EventOperator.ts b/packages/imperative/src/events/src/EventOperator.ts index 1b0261f2bc..d2a69d6ce2 100644 --- a/packages/imperative/src/events/src/EventOperator.ts +++ b/packages/imperative/src/events/src/EventOperator.ts @@ -77,6 +77,7 @@ export class EventOperator { * @internal Not meant to be called by application developers * @static * @returns {IZoweProcessor} The Zowe event processor instance. + * @throws {ImperativeError} If the application name is not recognized. */ public static getZoweProcessor(): IZoweProcessor { return this.createProcessor("Zowe", IProcessorTypes.BOTH, Logger.getAppLogger()); @@ -89,6 +90,7 @@ export class EventOperator { * @param {string} appName - The application name. * @param {Logger} [logger] - Optional logger for the processor. * @returns {IEmitterAndWatcher} An event processor capable of both emitting and watching. + * @throws {ImperativeError} If the application name is not recognized. */ public static getProcessor(appName: string, logger?: Logger): IEmitterAndWatcher { return this.createProcessor(appName, IProcessorTypes.BOTH, logger); @@ -101,6 +103,7 @@ export class EventOperator { * @param {string} appName - The application name, defaults to "Zowe" if not specified. * @param {Logger} [logger] - Optional logger for the processor. * @returns {IWatcher} A watcher-only event processor. + * @throws {ImperativeError} If the application name is not recognized. */ public static getWatcher(appName: string = "Zowe", logger?: Logger): IWatcher { return this.createProcessor(appName, IProcessorTypes.WATCHER, logger); @@ -113,6 +116,7 @@ export class EventOperator { * @param {string} appName - The application name. * @param {Logger} [logger] - Optional logger for the processor. * @returns {IEmitter} An emitter-only event processor. + * @throws {ImperativeError} If the application name is not recognized. */ public static getEmitter(appName: string, logger?: Logger): IEmitter { return this.createProcessor(appName, IProcessorTypes.EMITTER, logger); diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 55c623d472..2d452d6f91 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -50,6 +50,7 @@ export class EventProcessor { * @param {string} appName - The application's name. * @param {IProcessorTypes} type - The type of processor (Emitter, Watcher, or Both). * @param {Logger} [logger] - Optional logger for recording events and errors. + * @throws {ImperativeError} If the application name is not recognized. */ public constructor(appName: string, type: IProcessorTypes, logger?: Logger) { EventUtils.validateAppName(appName); @@ -67,46 +68,51 @@ export class EventProcessor { this.logger = logger ?? Logger.getImperativeLogger(); } + private _subscribe(type: 'user' | 'shared', eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { + if (this.processorType === IProcessorTypes.EMITTER) { + throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); + } + + let eventType; + if (type === "shared") { + eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : EventTypes.SharedEvents; + } else if (type === "user") { + eventType = EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.UserEvents; + } + + const disposable = EventUtils.createSubscription(this, eventName, eventType); + EventUtils.setupWatcher(this, eventName, callbacks); + return disposable; + } /** * Subscription to an event that will notify all subscribed users. * * @param {string} eventName - The name of the event to subscribe to. * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. - */ + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + */ public subscribeShared(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { - if (this.processorType === IProcessorTypes.EMITTER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); - } - const isZoweEvent = EventUtils.isSharedEvent(eventName); - const eventType = isZoweEvent ? EventTypes.ZoweSharedEvents : EventTypes.SharedEvents; - const disposable = EventUtils.createSubscription(this, eventName, eventType); - EventUtils.setupWatcher(this, eventName, callbacks); - return disposable; + return this._subscribe("shared", eventName, callbacks); } /** * Subscription to an event that will notify a single user. * - * @param {string} eventName - The name of the event to subscribe to. - * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. - * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. - */ + * @param {string} eventName - The name of the event to subscribe to. + * @param {EventCallback[] | EventCallback} callbacks - Callback functions to handle the event. + * @returns {IEventDisposable} - Object allowing management/cleanup of the subscription. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. + */ public subscribeUser(eventName: string, callbacks: EventCallback[] | EventCallback): IEventDisposable { - if (this.processorType === IProcessorTypes.EMITTER) { - throw new ImperativeError({ msg: `Processor does not have correct permissions: ${eventName}` }); - } - const isZoweEvent = EventUtils.isUserEvent(eventName); - const eventType = isZoweEvent ? EventTypes.ZoweUserEvents : EventTypes.UserEvents; - const disposable = EventUtils.createSubscription(this, eventName, eventType); - EventUtils.setupWatcher(this, eventName, callbacks); - return disposable; + return this._subscribe("user", eventName, callbacks); } /** * Private method to emit the event * @private * @param eventName Event to be emitted + * @throws {ImperativeError} - If the event cannot be emitted. */ private emit(eventName: string) { try { @@ -122,6 +128,7 @@ export class EventProcessor { * Emits an event by updating its timestamp and writing event data. * * @param {string} eventName - The name of the event to emit. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. * @throws {ImperativeError} - If the event cannot be emitted. */ public emitEvent(eventName: string): void { @@ -139,6 +146,7 @@ export class EventProcessor { * * @internal Internal Zowe emitter method * @param {string} eventName - The name of the Zowe event to emit. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. * @throws {ImperativeError} - If the event cannot be emitted. */ public emitZoweEvent(eventName: string): void { @@ -155,6 +163,7 @@ export class EventProcessor { * Unsubscribes from an event, closing file watchers and cleaning up resources. * * @param {string} eventName - The name of the event to unsubscribe from. + * @throws {ImperativeError} If the processor does not have the correct permissions to perform this action. * @throws {ImperativeError} - If unsubscribing fails. */ public unsubscribe(eventName: string): void { diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 29f9ca311c..5ab364f452 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -32,6 +32,8 @@ export class EventUtils { * * @static * @returns {string[]} List of application names. + * @throws If the extenders.json file cannot be created when it does not exist. + * @throws If the extenders.json file cannot be read. */ public static getListOfApps(): string[] { const extendersJson = ConfigUtils.readExtendersJson(); @@ -43,6 +45,7 @@ export class EventUtils { * * @static * @param {string} appName - The name of the application. + * @throws {ImperativeError} If the application name is not recognized. */ public static validateAppName(appName: string): void { const appList = this.getListOfApps(); @@ -81,6 +84,7 @@ export class EventUtils { * @internal This is not intended for application developers * @param eventFilePath The path to the event file * @returns The object representing the Event + * @throws {ImperativeError} If the contents of the event cannot be retrieved. */ public static getEventContents(eventFilePath: string): IEventJson { try { @@ -95,6 +99,7 @@ export class EventUtils { * * @param {string} appName - The name of the application. * @return {string} The directory path. + * @throws {ImperativeError} If the application name is not recognized. */ public static getEventDir(appName: string): string { this.validateAppName(appName); @@ -105,11 +110,12 @@ export class EventUtils { * Ensures that the specified directory for storing event files exists, creating it if necessary. * * @param {string} directoryPath - The path to the directory. + * @throws {ImperativeError} If we are unable to create the '.events' directory. */ public static ensureEventsDirExists(directoryPath: string) { try { if (!fs.existsSync(directoryPath)) { - fs.mkdirSync(directoryPath, {mode: 0o750, recursive: true}); // user read/write/exec, group read/exec + fs.mkdirSync(directoryPath, { mode: 0o750, recursive: true }); // user read/write/exec, group read/exec } } catch (err) { throw new ImperativeError({ msg: `Unable to create '.events' directory. Path: ${directoryPath}`, causeErrors: err }); @@ -120,13 +126,14 @@ export class EventUtils { * Ensures that the specified file path for storing event data exists, creating it if necessary. * * @param {string} filePath - The path to the file. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. */ public static ensureFileExists(filePath: string) { try { const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o640); fs.closeSync(fd); } catch (err) { - if (err.code!=='EEXIST'){ + if (err.code !== 'EEXIST') { throw new ImperativeError({ msg: `Unable to create event file. Path: ${filePath}`, causeErrors: err }); } } @@ -139,6 +146,9 @@ export class EventUtils { * @param eventName The name of the event. * @param appName The name of the application. * @returns The created event + * @throws {ImperativeError} If the application name is not recognized. + * @throws {ImperativeError} If we are unable to create the '.events' directory. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. */ public static createEvent(eventName: string, appName: string): Event { const zoweDir = ConfigUtils.getZoweDir(); @@ -164,6 +174,9 @@ export class EventUtils { * @param {string} eventName - The name of the event. * @param {EventTypes} eventType - The type of event. * @return {IEventDisposable} An interface for managing the subscription. + * @throws {ImperativeError} If the application name is not recognized. + * @throws {ImperativeError} If we are unable to create the '.events' directory. + * @throws {ImperativeError} If we are unable to create the event file required for event emission. */ public static createSubscription(eeInst: EventProcessor, eventName: string, eventType: EventTypes): IEventDisposable { const newEvent = EventUtils.createEvent(eventName, eeInst.appName); @@ -181,6 +194,8 @@ export class EventUtils { * @param {string} eventName - The name of the event. * @param {EventCallback[] | EventCallback} callbacks - A single callback or an array of callbacks to execute. * @return {fs.FSWatcher} A file system watcher. + * @throws {ImperativeError} If the event to be watched does not have an existing file to watch. + * @throws {ImperativeError} Callbacks will fail if the contents of the event cannot be retrieved. */ public static setupWatcher(eeInst: EventProcessor, eventName: string, callbacks: EventCallback[] | EventCallback): fs.FSWatcher { const event = eeInst.subscribedEvents.get(eventName); From ac67acc9d01ec2514c1cb3a022c13a1c5d85a79e Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Tue, 2 Jul 2024 10:16:52 -0400 Subject: [PATCH 70/72] lint: address all lint warnings :yum: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...Operator_and_Processor.integration.test.ts | 53 ++++++++++--------- .../src/events/src/EventProcessor.ts | 19 ++++--- .../imperative/src/events/src/EventUtils.ts | 1 + 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts index 2d53d7f47a..12c1741456 100644 --- a/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts +++ b/packages/imperative/src/events/__tests__/__integration__/EventOperator_and_Processor.integration.test.ts @@ -39,7 +39,7 @@ describe("Event Operator and Processor", () => { }); // have to reset because test environment doesn't add .zowe to ZOWE_CLI_HOME :( process.env.ZOWE_CLI_HOME = path.join(process.env.ZOWE_CLI_HOME || '', ".zowe"); - zoweCliHome = process.env.ZOWE_CLI_HOME; + zoweCliHome = process.env.ZOWE_CLI_HOME; EventUtils.ensureEventsDirExists(zoweCliHome); const extJson: IExtendersJsonOpts = ConfigUtils.readExtendersJson(); sampleApps.forEach(app => extJson.profileTypes[app] = { from: [app] }); @@ -148,30 +148,31 @@ describe("Event Operator and Processor", () => { }); describe("Custom Events", () => { - it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", async () => { - const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); - const callback = jest.fn(); - const Watcher = EventOperator.getWatcher(sampleApps[1]); - const Emitter = EventOperator.getEmitter(sampleApps[1]); - const eventDir = path.join(zoweCliHome, ".events", sampleApps[1]); //corresponds to emitter's event file - - expect((Watcher as EventProcessor).subscribedEvents.get(customUserEvent)).toBeFalsy(); - - // Subscribe to event - Watcher.subscribeUser(customUserEvent, callback); - const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customUserEvent).toJson(); - expect(callback).not.toHaveBeenCalled(); - expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); - - // Emit event and trigger callback - Emitter.emitEvent(customUserEvent); - setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); - - expect(eventDetails.eventName).toEqual(customUserEvent); - expect(eventDetails.eventFilePath).toContain(eventDir); - expect(callback).toHaveBeenCalled(); - expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeFalsy(); //ensuring this custom event isnt a Zowe event - EventOperator.deleteProcessor(sampleApps[1]); - }); + it("should create an event file upon first subscription if file does not exist - specific to CustomUserEvent directory structure", + async () => { + const setupWatcherSpy = jest.spyOn(EventUtils, "setupWatcher"); + const callback = jest.fn(); + const Watcher = EventOperator.getWatcher(sampleApps[1]); + const Emitter = EventOperator.getEmitter(sampleApps[1]); + const eventDir = path.join(zoweCliHome, ".events", sampleApps[1]); //corresponds to emitter's event file + + expect((Watcher as EventProcessor).subscribedEvents.get(customUserEvent)).toBeFalsy(); + + // Subscribe to event + Watcher.subscribeUser(customUserEvent, callback); + const eventDetails: IEventJson = (Watcher as any).subscribedEvents.get(customUserEvent).toJson(); + expect(callback).not.toHaveBeenCalled(); + expect(fs.existsSync(eventDetails.eventFilePath)).toBeTruthy(); + + // Emit event and trigger callback + Emitter.emitEvent(customUserEvent); + setupWatcherSpy.mock.calls.forEach(call => (call[2] as Function)()); + + expect(eventDetails.eventName).toEqual(customUserEvent); + expect(eventDetails.eventFilePath).toContain(eventDir); + expect(callback).toHaveBeenCalled(); + expect(EventUtils.isUserEvent(eventDetails.eventName)).toBeFalsy(); //ensuring this custom event isnt a Zowe event + EventOperator.deleteProcessor(sampleApps[1]); + }); }); }); diff --git a/packages/imperative/src/events/src/EventProcessor.ts b/packages/imperative/src/events/src/EventProcessor.ts index 2d452d6f91..f012da1f64 100644 --- a/packages/imperative/src/events/src/EventProcessor.ts +++ b/packages/imperative/src/events/src/EventProcessor.ts @@ -26,13 +26,18 @@ import { IProcessorTypes } from "./doc/IEventInstanceTypes"; * It uses a map where event names are keys, and values are Event objects that hold detailed event information and subscriptions. * * An `EventProcessor` handles three main functionalities: - * - **Subscribing to Events**: Registration of a callback function that will be executed when that event occurs. - * - **Emitting Events**: Notifying other applications or parts of the same application about certain actions or changes. - * - **Managing Event Subscriptions**: Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. + * - **Subscribing to Events**: + * Registration of a callback function that will be executed when that event occurs. + * - **Emitting Events**: + * Notifying other applications or parts of the same application about certain actions or changes. + * - **Managing Event Subscriptions**: + * Mapping subscribed events and their corresponding callbacks, ensuring that events are properly handled and dispatched. * * ### Understanding Event Types - * - **Predefined Zowe Events**: Zowe provides a set of predefined shared and user events that can be watched. - * - **Custom Events**: Applications can define their own shared and user events, allowing for custom event-driven behavior. + * - **Predefined Zowe Events**: + * Zowe provides a set of predefined shared and user events that can be watched. + * - **Custom Events**: + * Applications can define their own shared and user events, allowing for custom event-driven behavior. * * @export * @class EventProcessor @@ -78,8 +83,8 @@ export class EventProcessor { eventType = EventUtils.isSharedEvent(eventName) ? EventTypes.ZoweSharedEvents : EventTypes.SharedEvents; } else if (type === "user") { eventType = EventUtils.isUserEvent(eventName) ? EventTypes.ZoweUserEvents : EventTypes.UserEvents; - } - + } + const disposable = EventUtils.createSubscription(this, eventName, eventType); EventUtils.setupWatcher(this, eventName, callbacks); return disposable; diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 5ab364f452..91960bfa69 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -130,6 +130,7 @@ export class EventUtils { */ public static ensureFileExists(filePath: string) { try { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers const fd = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR, 0o640); fs.closeSync(fd); } catch (err) { From 8cb0253283be199c06306e9b0e361ed485aafc50 Mon Sep 17 00:00:00 2001 From: zFernand0 <37381190+zFernand0@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:35:40 -0400 Subject: [PATCH 71/72] tests: fix integration tests :tada: Signed-off-by: zFernand0 <37381190+zFernand0@users.noreply.github.com> --- ...ive.test.cli.config.auto-init.fruit.integration.subtest.ts | 4 ++-- ...ve-test-cli.config.convert-profiles.integration.subtest.ts | 1 + .../src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts | 4 +++- packages/imperative/src/config/src/ConfigUtils.ts | 3 ++- packages/imperative/src/events/src/EventUtils.ts | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts index 4e9ad9c6fe..ffbf94c9ec 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/auto-init/imperative.test.cli.config.auto-init.fruit.integration.subtest.ts @@ -118,7 +118,7 @@ describe("cmd-cli config auto-init", () => { expect(response.status).toBe(0); expect(glob.sync("*.json", { cwd: TEST_ENVIRONMENT.workingDir })) - .toEqual(["imperative-test-cli.config.json", "imperative-test-cli.schema.json"]); + .toEqual(["extenders.json", "imperative-test-cli.config.json", "imperative-test-cli.schema.json"]); const configJson: IConfig = jsonfile.readFileSync(TEST_ENVIRONMENT.workingDir + "/imperative-test-cli.config.json"); expect(configJson.profiles.base_fruit).toBeDefined(); expect(configJson.profiles.base_fruit.properties.tokenValue).toBeUndefined(); @@ -135,7 +135,7 @@ describe("cmd-cli config auto-init", () => { expect(response.status).toBe(0); expect(glob.sync("*.json", { cwd: TEST_ENVIRONMENT.workingDir })) - .toEqual(["imperative-test-cli.config.user.json", "imperative-test-cli.schema.json"]); + .toEqual(["extenders.json", "imperative-test-cli.config.user.json", "imperative-test-cli.schema.json"]); const configJson: IConfig = jsonfile.readFileSync(TEST_ENVIRONMENT.workingDir + "/imperative-test-cli.config.user.json"); expect(configJson.profiles.base_fruit).toBeDefined(); expect(configJson.profiles.base_fruit.properties.tokenValue).toBeUndefined(); diff --git a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts index 6263057a56..8025b02006 100644 --- a/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts +++ b/packages/imperative/__tests__/__integration__/imperative/__tests__/__integration__/cli/config/convert-profiles/cli.imperative-test-cli.config.convert-profiles.integration.subtest.ts @@ -30,6 +30,7 @@ describe("imperative-test-cli config convert-profiles", () => { testName: "imperative_test_cli_test_config_convert_profiles_command" }); configJsonPath = path.join(process.env.IMPERATIVE_TEST_CLI_CLI_HOME as string, "imperative-test-cli.config.json"); + // jest.spyOn(EventOperator, "getZoweProcessor").mockReturnValue({emitZoweEvent: jest.fn()} as any); }); beforeEach(() => { diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index 0f62894524..5bd89d66bc 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -1360,7 +1360,9 @@ describe("TeamConfig ProfileInfo tests", () => { { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: false }, // TODO(zFernand0): Investigate why only the team project is present in the osLoc array // Possible reason: global layer not loaded by getAllProfiles() - // { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: true } + // ---- + // Reseting the loaded configuration in `getZoweDir` may be the root cause. + { name: profAttrs.profName, path: profAttrs.profLoc.osLoc?.[0], user: false, global: true } ]; expect(osLocInfo).toBeDefined(); expect(osLocInfo.length).toBe(expectedObjs.length); diff --git a/packages/imperative/src/config/src/ConfigUtils.ts b/packages/imperative/src/config/src/ConfigUtils.ts index 22767fcd5e..2bcb7141df 100644 --- a/packages/imperative/src/config/src/ConfigUtils.ts +++ b/packages/imperative/src/config/src/ConfigUtils.ts @@ -50,7 +50,8 @@ export class ConfigUtils { * @throws If the extenders.json file cannot be read. */ public static readExtendersJson(): IExtendersJsonOpts { - const extenderJsonPath = pathJoin(ConfigUtils.getZoweDir(), "extenders.json"); + const cliHome = ImperativeConfig.instance.loadedConfig != null ? ImperativeConfig.instance.cliHome : ConfigUtils.getZoweDir(); + const extenderJsonPath = pathJoin(cliHome, "extenders.json"); if (!fsExistsSync(extenderJsonPath)) { jsonfile.writeFileSync(extenderJsonPath, { profileTypes: {} diff --git a/packages/imperative/src/events/src/EventUtils.ts b/packages/imperative/src/events/src/EventUtils.ts index 91960bfa69..7bf5a8e4f4 100644 --- a/packages/imperative/src/events/src/EventUtils.ts +++ b/packages/imperative/src/events/src/EventUtils.ts @@ -17,6 +17,7 @@ import { ConfigUtils } from "../../config/src/ConfigUtils"; import { IEventDisposable, IEventJson } from "./doc"; import { Event } from "./Event"; import { EventProcessor } from "./EventProcessor"; +import { ImperativeConfig } from "../../utilities/src/ImperativeConfig"; /** * A collection of helper functions related to event processing, including: @@ -152,7 +153,7 @@ export class EventUtils { * @throws {ImperativeError} If we are unable to create the event file required for event emission. */ public static createEvent(eventName: string, appName: string): Event { - const zoweDir = ConfigUtils.getZoweDir(); + const zoweDir = ImperativeConfig.instance.loadedConfig != null ? ImperativeConfig.instance.cliHome : ConfigUtils.getZoweDir(); const dir = join(zoweDir, EventUtils.getEventDir(appName)); this.ensureEventsDirExists(dir); From 407bdac988812489e0d03e19145670c766ac08d2 Mon Sep 17 00:00:00 2001 From: zowe-robot Date: Thu, 11 Jul 2024 21:51:03 +0000 Subject: [PATCH 72/72] Bump version to 8.0.0-next.202407112150 [ci skip] Signed-off-by: zowe-robot --- .../__packages__/cli-test-utils/package.json | 4 +- lerna.json | 2 +- npm-shrinkwrap.json | 122 +++++++++--------- packages/cli/package.json | 28 ++-- packages/core/package.json | 6 +- packages/imperative/CHANGELOG.md | 2 +- packages/imperative/package.json | 4 +- packages/provisioning/package.json | 8 +- packages/secrets/package.json | 2 +- packages/workflows/package.json | 10 +- packages/zosconsole/package.json | 8 +- packages/zosfiles/package.json | 10 +- packages/zosjobs/package.json | 10 +- packages/zoslogs/package.json | 8 +- packages/zosmf/package.json | 8 +- packages/zostso/package.json | 10 +- packages/zosuss/package.json | 6 +- 17 files changed, 124 insertions(+), 124 deletions(-) diff --git a/__tests__/__packages__/cli-test-utils/package.json b/__tests__/__packages__/cli-test-utils/package.json index a83dddbe8d..db4696f778 100644 --- a/__tests__/__packages__/cli-test-utils/package.json +++ b/__tests__/__packages__/cli-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Test utilities package for Zowe CLI plug-ins", "author": "Zowe", "license": "EPL-2.0", @@ -43,7 +43,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/lerna.json b/lerna.json index 5c27a61411..f40da649e2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "command": { "publish": { "ignoreChanges": [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 83710a78e3..bb74c2aa5f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -52,7 +52,7 @@ }, "__tests__/__packages__/cli-test-utils": { "name": "@zowe/cli-test-utils", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "find-up": "^5.0.0", @@ -63,7 +63,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" @@ -16341,21 +16341,21 @@ }, "packages/cli": { "name": "@zowe/cli", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -16368,7 +16368,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/cli-test-utils": "8.0.0-next.202407112150", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" @@ -16377,7 +16377,7 @@ "node": ">=18.12.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150" } }, "packages/cli/node_modules/brace-expansion": { @@ -16424,15 +16424,15 @@ }, "packages/core": { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "comment-json": "~4.2.3", "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16443,7 +16443,7 @@ }, "packages/imperative": { "name": "@zowe/imperative", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "@types/yargs": "^17.0.32", @@ -16497,7 +16497,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", @@ -16646,16 +16646,16 @@ }, "packages/provisioning": { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "js-yaml": "^4.1.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16667,7 +16667,7 @@ }, "packages/secrets": { "name": "@zowe/secrets-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "hasInstallScript": true, "license": "EPL-2.0", "devDependencies": { @@ -16680,15 +16680,15 @@ }, "packages/workflows": { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16700,12 +16700,12 @@ }, "packages/zosconsole": { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16717,16 +16717,16 @@ }, "packages/zosfiles": { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16758,15 +16758,15 @@ }, "packages/zosjobs": { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16778,12 +16778,12 @@ }, "packages/zoslogs": { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16795,12 +16795,12 @@ }, "packages/zosmf": { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16812,15 +16812,15 @@ }, "packages/zostso": { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" @@ -16832,15 +16832,15 @@ }, "packages/zosuss": { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "license": "EPL-2.0", "dependencies": { "ssh2": "^1.15.0" }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 5762a5938d..70fa9b4896 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "zoweVersion": "V3", "description": "Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.", "author": "Zowe", @@ -58,17 +58,17 @@ "preshrinkwrap": "node ../../scripts/rewriteShrinkwrap.js" }, "dependencies": { - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/provisioning-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-console-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-jobs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-logs-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-tso-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zos-workflows-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -78,13 +78,13 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", + "@zowe/cli-test-utils": "8.0.0-next.202407112150", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" }, "optionalDependencies": { - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150" }, "engines": { "node": ">=18.12.0" diff --git a/packages/core/package.json b/packages/core/package.json index 1c07861c3a..f970c2d6d6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/core-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Core libraries shared by Zowe SDK packages", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next" diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index 469c090abd..46311ef368 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to the Imperative package will be documented in this file. -## Recent Changes +## `8.0.0-next.202407112150` - Enhancement: Add client-side custom-event handling capabilities. [#2136](https://github.com/zowe/zowe-cli/pull/2136) - Next-Breaking: Refactored the Imperative Event Emitter class. [#2136](https://github.com/zowe/zowe-cli/pull/2136) diff --git a/packages/imperative/package.json b/packages/imperative/package.json index 0376a3b00f..122c32d3bd 100644 --- a/packages/imperative/package.json +++ b/packages/imperative/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/imperative", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "framework for building configurable CLIs", "author": "Zowe", "license": "EPL-2.0", @@ -94,7 +94,7 @@ "@types/pacote": "^11.1.8", "@types/progress": "^2.0.7", "@types/stack-trace": "^0.0.33", - "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407051717", + "@zowe/secrets-for-zowe-sdk": "8.0.0-next.202407112150", "concurrently": "^8.0.0", "cowsay": "^1.6.0", "deep-diff": "^1.0.0", diff --git a/packages/provisioning/package.json b/packages/provisioning/package.json index 86888b30d8..01992a178a 100644 --- a/packages/provisioning/package.json +++ b/packages/provisioning/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS provisioning APIs", "author": "Zowe", "license": "EPL-2.0", @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/secrets/package.json b/packages/secrets/package.json index 25fdb5d971..cf50460549 100644 --- a/packages/secrets/package.json +++ b/packages/secrets/package.json @@ -3,7 +3,7 @@ "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", "repository": "https://github.com/zowe/zowe-cli.git", "author": "Zowe", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", "bugs": { "url": "https://github.com/zowe/zowe-cli/issues" diff --git a/packages/workflows/package.json b/packages/workflows/package.json index 6756a80d57..16cbf9a995 100644 --- a/packages/workflows/package.json +++ b/packages/workflows/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS workflows APIs", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosconsole/package.json b/packages/zosconsole/package.json index abf58e59db..0ca316dda3 100644 --- a/packages/zosconsole/package.json +++ b/packages/zosconsole/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS console", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosfiles/package.json b/packages/zosfiles/package.json index 2c4574d1e0..b447a1353e 100644 --- a/packages/zosfiles/package.json +++ b/packages/zosfiles/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with files and data sets on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,10 +49,10 @@ "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717", - "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150", + "@zowe/zos-uss-for-zowe-sdk": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosjobs/package.json b/packages/zosjobs/package.json index 455536a3a7..ec97a0f530 100644 --- a/packages/zosjobs/package.json +++ b/packages/zosjobs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with jobs on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -46,12 +46,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zos-files-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zoslogs/package.json b/packages/zoslogs/package.json index f1b7da5a79..8ca33f75c2 100644 --- a/packages/zoslogs/package.json +++ b/packages/zoslogs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS logs", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosmf/package.json b/packages/zosmf/package.json index eb6dd8c5cc..4b7f87e619 100644 --- a/packages/zosmf/package.json +++ b/packages/zosmf/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with the z/OS Management Facility", "author": "Zowe", "license": "EPL-2.0", @@ -44,9 +44,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zostso/package.json b/packages/zostso/package.json index 829fcd438b..eac90956d1 100644 --- a/packages/zostso/package.json +++ b/packages/zostso/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with TSO on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407051717" + "@zowe/zosmf-for-zowe-sdk": "8.0.0-next.202407112150" }, "devDependencies": { - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/core-for-zowe-sdk": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/core-for-zowe-sdk": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0-next", diff --git a/packages/zosuss/package.json b/packages/zosuss/package.json index 5a6790754c..529f219a42 100644 --- a/packages/zosuss/package.json +++ b/packages/zosuss/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.0.0-next.202407051717", + "version": "8.0.0-next.202407112150", "description": "Zowe SDK to interact with USS on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.0.0-next.202407051717", - "@zowe/imperative": "8.0.0-next.202407051717" + "@zowe/cli-test-utils": "8.0.0-next.202407112150", + "@zowe/imperative": "8.0.0-next.202407112150" }, "peerDependencies": { "@zowe/imperative": "^8.0.0-next"