From c0ea753c70afd1348e10cc639d9e1487e2ce4810 Mon Sep 17 00:00:00 2001 From: Norbert Csaba Herczeg Date: Tue, 12 Dec 2023 21:37:32 +0100 Subject: [PATCH] feat(scr): fix deactivation and update events --- .../configuration-management/package.json | 1 + .../src/configuration-impl.test.ts | 159 ++++++++++++------ .../src/configuration-manager.test.ts | 3 +- .../src/configuration-manager.ts | 44 +++-- .../lib/framework/bundle-context-impl.test.ts | 10 ++ .../framework/service-registry-impl.test.ts | 14 ++ packages/@pandino/scr/package.json | 2 + .../scr/src/ComponentConfigurationImpl.ts | 2 +- .../scr/src/ServiceComponentRuntimeImpl.ts | 13 +- packages/@pandino/scr/src/index.test.ts | 71 +++++++- pnpm-lock.yaml | 3 + 11 files changed, 244 insertions(+), 78 deletions(-) diff --git a/packages/@pandino/configuration-management/package.json b/packages/@pandino/configuration-management/package.json index 8f679e6..a4db0d2 100644 --- a/packages/@pandino/configuration-management/package.json +++ b/packages/@pandino/configuration-management/package.json @@ -50,6 +50,7 @@ "@pandino/pandino": "workspace:^0.8.31", "@pandino/pandino-api": "workspace:^0.8.31", "@pandino/persistence-manager-api": "workspace:^0.8.31", + "@pandino/persistence-manager-memory": "workspace:^0.8.31", "@pandino/rollup-plugin-generate-manifest": "workspace:^0.8.31", "dts-bundle-generator": "^8.1.2", "rimraf": "^5.0.5", diff --git a/packages/@pandino/configuration-management/src/configuration-impl.test.ts b/packages/@pandino/configuration-management/src/configuration-impl.test.ts index a212c57..2838274 100644 --- a/packages/@pandino/configuration-management/src/configuration-impl.test.ts +++ b/packages/@pandino/configuration-management/src/configuration-impl.test.ts @@ -1,32 +1,86 @@ -import { describe, beforeEach, expect, it, vi } from 'vitest'; -import type { Bundle, BundleContext, Logger, ServiceProperties, ServiceReference } from '@pandino/pandino-api'; +import { describe, beforeEach, expect, it, vi, afterEach } from 'vitest'; +import { + Bundle, + BUNDLE_ACTIVATOR, + BUNDLE_SYMBOLICNAME, + BUNDLE_VERSION, + BundleContext, + type BundleImporter, + BundleManifestHeaders, + FrameworkConfigMap, + LOG_LEVEL_PROP, + LogLevel, + PANDINO_BUNDLE_IMPORTER_PROP, + PANDINO_MANIFEST_FETCHER_PROP, + PROVIDE_CAPABILITY, + REQUIRE_CAPABILITY, + ServiceProperties, + ServiceReference, +} from '@pandino/pandino-api'; import { SERVICE_PID } from '@pandino/pandino-api'; -import { CONFIGURATION_LISTENER_INTERFACE_KEY, MANAGED_SERVICE_INTERFACE_KEY } from '@pandino/configuration-management-api'; +import { + CONFIG_ADMIN_INTERFACE_KEY, + CONFIGURATION_LISTENER_INTERFACE_KEY, + ConfigurationAdmin, + MANAGED_SERVICE_INTERFACE_KEY, +} from '@pandino/configuration-management-api'; +import PMActivator from '@pandino/persistence-manager-memory'; import type { Configuration, ConfigurationEvent, ConfigurationEventType, ConfigurationListener, ManagedService } from '@pandino/configuration-management-api'; -import { evaluateFilter } from '@pandino/filters'; -import { MockBundleContext } from './__mocks__/mock-bundle-context'; -import { MockBundle } from './__mocks__/mock-bundle'; -import { MockPersistenceManager } from './__mocks__/mock-persistence-manager'; +import Pandino from '@pandino/pandino'; import { ConfigurationAdminImpl } from './configuration-admin-impl'; -import { ConfigurationManager } from './configuration-manager'; +import { Activator as CMActivator } from './activator'; describe('ConfigurationImpl', () => { - let context: BundleContext; + let params: FrameworkConfigMap; let bundle: Bundle; + let pandino: Pandino; + let pandinoContext: BundleContext; + const pmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/persistence-manager-memory', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new PMActivator(), + [PROVIDE_CAPABILITY]: '@pandino/persistence-manager;type="in-memory";objectClass="@pandino/persistence-manager/PersistenceManager"', + }); + const cmHeaders: () => BundleManifestHeaders = () => ({ + [BUNDLE_SYMBOLICNAME]: '@pandino/configuration-management', + [BUNDLE_VERSION]: '0.0.0', + [BUNDLE_ACTIVATOR]: new CMActivator(), + [REQUIRE_CAPABILITY]: '@pandino/persistence-manager;filter:=(objectClass=@pandino/persistence-manager/PersistenceManager)', + [PROVIDE_CAPABILITY]: + '@pandino/configuration-management;objectClass:Array="@pandino/configuration-management/ConfigurationAdmin,@pandino/configuration-management/ManagedService,@pandino/configuration-management/ConfigurationListener"', + }); + const importer: BundleImporter = { + import: (activatorLocation: string, manifestLocation: string, deploymentRoot?: string) => { + // this won't be called if the activator is an instance and not a string + return Promise.resolve({ + default: null as unknown as any, + }); + }, + }; let configAdmin: ConfigurationAdminImpl; - let cm: ConfigurationManager; - let mockDebug = vi.fn(); - let logger: Logger = { - debug: mockDebug, - } as unknown as Logger; - - beforeEach(() => { - mockDebug.mockClear(); - context = new MockBundleContext(); - bundle = new MockBundle(context as MockBundleContext, 'test.bundle.location', '@test/my-bundle', '0.0.0'); - cm = new ConfigurationManager(context, logger, evaluateFilter, new MockPersistenceManager('{}')); - context.addServiceListener(cm); - configAdmin = new ConfigurationAdminImpl(cm, bundle, logger); + + beforeEach(async () => { + params = { + [PANDINO_MANIFEST_FETCHER_PROP]: vi.fn() as any, + [PANDINO_BUNDLE_IMPORTER_PROP]: importer, + [LOG_LEVEL_PROP]: LogLevel.WARN, + }; + pandino = new Pandino(params); + + await pandino.init(); + await pandino.start(); + + pandinoContext = pandino.getBundleContext(); + const [pmb, cmb] = await installDepBundles(pandinoContext); + bundle = pandino; + const ref = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + configAdmin = pandinoContext.getService(ref) as ConfigurationAdminImpl; + }); + + afterEach(() => { + pandino.stop(); + pandino = undefined; + pandinoContext = undefined; }); it('basic creation of configuration', () => { @@ -52,7 +106,7 @@ describe('ConfigurationImpl', () => { // configuration didn't register a location testConfiguration(configuration, 'test.pid', undefined, undefined); - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -91,7 +145,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -119,7 +173,7 @@ describe('ConfigurationImpl', () => { updated: mockUpdated, }; configuration.update(); - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -134,7 +188,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -158,7 +212,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toContain({ [SERVICE_PID]: 'test.pid', }); }); @@ -174,11 +228,11 @@ describe('ConfigurationImpl', () => { const service2: ManagedService = { updated: mockUpdated2, }; - const registration1 = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service1, { + const registration1 = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service1, { [SERVICE_PID]: 'test.pid', name: 'service1', }); - const registration2 = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service2, { + const registration2 = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service2, { [SERVICE_PID]: 'test.pid', name: 'service2', }); @@ -210,11 +264,11 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration1.getProperties()).toEqual({ + expect(registration1.getProperties()).toContain({ name: 'service1', [SERVICE_PID]: 'test.pid', }); - expect(registration2.getProperties()).toEqual({ + expect(registration2.getProperties()).toContain({ name: 'service2', [SERVICE_PID]: 'test.pid', }); @@ -226,7 +280,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -252,11 +306,13 @@ describe('ConfigurationImpl', () => { }); it('events', () => { + const mockUpdatedEvent = vi.fn(); const mockConfigurationEvent = vi.fn(); - const listener: ConfigurationListener = { + const service: ManagedService & ConfigurationListener = { + updated: mockUpdatedEvent, configurationEvent: mockConfigurationEvent, }; - context.registerService(CONFIGURATION_LISTENER_INTERFACE_KEY, listener, { + const registration = pandinoContext.registerService([MANAGED_SERVICE_INTERFACE_KEY, CONFIGURATION_LISTENER_INTERFACE_KEY], service, { [SERVICE_PID]: 'test.pid', }); @@ -264,26 +320,27 @@ describe('ConfigurationImpl', () => { testConfigurationEvent(mockConfigurationEvent, 0); - const service: ManagedService = { - updated: vi.fn(), - }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { - [SERVICE_PID]: 'test.pid', - }); - const reference = registration.getReference(); + expect(registration.getReference().hasObjectClass(MANAGED_SERVICE_INTERFACE_KEY)).toBe(true); + expect(registration.getReference().hasObjectClass(CONFIGURATION_LISTENER_INTERFACE_KEY)).toBe(true); - testConfigurationEvent(mockConfigurationEvent, 1, 'UPDATED', reference); + // const managedReference = pandinoContext.getServiceReference(MANAGED_SERVICE_INTERFACE_KEY); + // const listenerReference = pandinoContext.getServiceReference(CONFIGURATION_LISTENER_INTERFACE_KEY); + + expect(service.updated).toHaveBeenCalledTimes(1); + expect(service.configurationEvent).toHaveBeenCalledTimes(0); + testConfigurationEvent(mockConfigurationEvent, 0, 'UPDATED', registration.getReference()); configuration.update({ prop1: true, prop2: 'test', }); - testConfigurationEvent(mockConfigurationEvent, 2, 'UPDATED', reference); + expect(service.updated).toHaveBeenCalledTimes(2); + testConfigurationEvent(mockConfigurationEvent, 1, 'UPDATED', registration.getReference()); configuration.delete(); - testConfigurationEvent(mockConfigurationEvent, 3, 'DELETED', reference); + testConfigurationEvent(mockConfigurationEvent, 2, 'DELETED', registration.getReference()); }); it('targetpid matching use-case based on bundle symbolic name', () => { @@ -292,7 +349,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -318,7 +375,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toContain({ [SERVICE_PID]: 'test.pid', }); }); @@ -329,7 +386,7 @@ describe('ConfigurationImpl', () => { const service: ManagedService = { updated: mockUpdated, }; - const registration = context.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { + const registration = pandinoContext.registerService(MANAGED_SERVICE_INTERFACE_KEY, service, { [SERVICE_PID]: 'test.pid', }); @@ -348,7 +405,7 @@ describe('ConfigurationImpl', () => { prop2: 'test', }); - expect(registration.getProperties()).toEqual({ + expect(registration.getProperties()).toContain({ [SERVICE_PID]: 'test.pid', }); }); @@ -383,4 +440,10 @@ describe('ConfigurationImpl', () => { expect(event.getReference()).toEqual(reference); } } + + async function installDepBundles(ctx: BundleContext): Promise { + const pmb = await ctx.installBundle(pmHeaders()); + const cmb = await ctx.installBundle(cmHeaders()); + return [pmb, cmb]; + } }); diff --git a/packages/@pandino/configuration-management/src/configuration-manager.test.ts b/packages/@pandino/configuration-management/src/configuration-manager.test.ts index bd42c89..ebf1d9a 100644 --- a/packages/@pandino/configuration-management/src/configuration-manager.test.ts +++ b/packages/@pandino/configuration-management/src/configuration-manager.test.ts @@ -156,7 +156,8 @@ describe('ConfigurationManager', function () { expect((cm as any).eventListeners.size).toEqual(1); expect((cm as any).eventListeners.has('mock.pid')).toEqual(true); expect((cm as any).eventListeners.get('mock.pid').length).toEqual(1); - expect((cm as any).eventListeners.get('mock.pid')[0]).toEqual(mockService); + const asd = (cm as any).eventListeners.get('mock.pid')[0]; + expect((cm as any).eventListeners.get('mock.pid')[0]).toEqual(mockServiceReference); const unregisteringEvent: ServiceEvent = { getType: () => 'UNREGISTERING', diff --git a/packages/@pandino/configuration-management/src/configuration-manager.ts b/packages/@pandino/configuration-management/src/configuration-manager.ts index d923368..a82075f 100644 --- a/packages/@pandino/configuration-management/src/configuration-manager.ts +++ b/packages/@pandino/configuration-management/src/configuration-manager.ts @@ -14,7 +14,7 @@ export class ConfigurationManager implements ServiceListener { private readonly logger: Logger; private readonly evaluateFilter: FilterEvaluator; private readonly managedReferences: Map>> = new Map>>(); - private readonly eventListeners: Map = new Map(); + private readonly eventListeners: Map[]> = new Map[]>(); private readonly configurationCache: ConfigurationCache; constructor(context: BundleContext, logger: Logger, evaluateFilter: FilterEvaluator, pm: PersistenceManager) { @@ -75,9 +75,9 @@ export class ConfigurationManager implements ServiceListener { ? this.configurationCache.get(refPid)! : this.internalCreateConfiguration(refPid, reference.getBundle()?.getLocation()); this.handleManagedServiceEvent(event.getType(), refPid, reference, service, config); - } else if (typeof (service as ConfigurationListener).configurationEvent === 'function') { - const configurationListener: ConfigurationListener = service; - this.handleConfigurationEventListenerEvent(event.getType(), refPid, configurationListener); + } + if (typeof (service as ConfigurationListener).configurationEvent === 'function') { + this.handleConfigurationEventListenerEvent(event, refPid); } } } @@ -132,20 +132,20 @@ export class ConfigurationManager implements ServiceListener { } } - private handleConfigurationEventListenerEvent(eventType: ServiceEventType, refPid: string, configurationListener: ConfigurationListener): void { - if (eventType === 'REGISTERED') { + private handleConfigurationEventListenerEvent(event: ServiceEvent, refPid: string): void { + if (event.getType() === 'REGISTERED') { if (!this.eventListeners.has(refPid)) { this.eventListeners.set(refPid, []); } const listeners = this.eventListeners.get(refPid); - if (Array.isArray(listeners) && !listeners.includes(configurationListener)) { - listeners.push(configurationListener); + if (Array.isArray(listeners) && !listeners.includes(event.getServiceReference())) { + listeners.push(event.getServiceReference()); } - } else if (eventType === 'UNREGISTERING') { + } else if (event.getType() === 'UNREGISTERING') { if (this.eventListeners.has(refPid)) { const listeners = this.eventListeners.get(refPid); if (Array.isArray(listeners)) { - const listenerIdx = listeners.findIndex((l) => l === configurationListener); + const listenerIdx = listeners.findIndex((l) => l === event.getServiceReference()); if (listenerIdx > -1) { listeners.splice(listenerIdx, 1); } @@ -192,6 +192,7 @@ export class ConfigurationManager implements ServiceListener { deleteConfiguration(pid: string): void { if (this.configurationCache.has(pid)) { + const listeners = this.eventListeners.get(pid); this.configurationCache.delete(pid); if (this.managedReferences.has(pid)) { const refs = this.managedReferences.get(pid); @@ -201,10 +202,15 @@ export class ConfigurationManager implements ServiceListener { if (service) { service.updated(undefined); } - this.fireConfigurationChangeEvent('DELETED', pid, ref); + // this.fireConfigurationChangeEvent('DELETED', pid, ref); } } } + if (Array.isArray(listeners)) { + for (const listener of listeners) { + this.fireConfigurationChangeEvent('DELETED', pid, listener); + } + } } this.logger.debug(`Attempted to delete already removed configuration for pid: ${pid}, ignoring.`); } @@ -234,17 +240,6 @@ export class ConfigurationManager implements ServiceListener { return new ConfigurationImpl(this, pid, bundleLocation); } - // private storeConfiguration(configuration: ConfigurationImpl): ConfigurationImpl { - // const pid = configuration.getPid(); - // const existing = this.configurationCache.get(pid); - // if (existing) { - // return existing as ConfigurationImpl; - // } - // - // this.configurationCache.set(pid, configuration); - // return configuration; - // } - private fireConfigurationChangeEvent(type: ConfigurationEventType, pid: string, ref: ServiceReference): void { const event: ConfigurationEvent = { getPid(): string { @@ -259,7 +254,10 @@ export class ConfigurationManager implements ServiceListener { }; const listeners = this.eventListeners.get(pid) || []; for (const listener of listeners) { - listener.configurationEvent(event); + const service = this.context.getService(listener); + if (service) { + service!.configurationEvent(event); + } } } } diff --git a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts index 9f1f56f..c0cbaad 100644 --- a/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/bundle-context-impl.test.ts @@ -265,6 +265,16 @@ describe('BundleContextImpl', () => { expect(reference.getUsingBundles().length).toEqual(0); }); + it('getServiceReference() for multiple interfaces', () => { + bundleContext.registerService(['@scope/bundle/service1', '@scope/bundle/service2'], mockService); + const reference1: ServiceReference = bundleContext.getServiceReference('@scope/bundle/service1'); + const reference2: ServiceReference = bundleContext.getServiceReference('@scope/bundle/service2'); + + expect(reference1).toBeDefined(); + expect(reference2).toBeDefined(); + expect(reference1).toEqual(reference2); + }); + it('getServiceReferences() with proper filter', () => { const otherMockService: MockService = { execute(): boolean { diff --git a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts index 453c8a5..996c444 100644 --- a/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts +++ b/packages/@pandino/pandino/src/lib/framework/service-registry-impl.test.ts @@ -85,6 +85,20 @@ describe('ServiceRegistryImpl', () => { expect(regWelcome.getProperty(OBJECTCLASS)).toEqual('@pandino/pandino/welcome-impl'); }); + it('multiple interface implementation registration', () => { + const cls1 = '@pandino/pandino/one'; + const cls2 = '@pandino/pandino/two'; + const regMulti: ServiceRegistration = sr.registerService(bundle1, [cls1, cls2], helloService); + + expect(sr.getRegisteredServices(bundle1).length).toEqual(1); + + expect(sr.getServiceReferences(cls1).length).toEqual(1); + expect(sr.getServiceReferences(cls2).length).toEqual(1); + expect(regMulti.getProperty(OBJECTCLASS)).toEqual([cls1, cls2]); + expect(regMulti.getReference().hasObjectClass(cls1)).toEqual(true); + expect(regMulti.getReference().hasObjectClass(cls2)).toEqual(true); + }); + it('unregisterService()', () => { const reg: ServiceRegistration = sr.registerService(bundle1, '@pandino/pandino/hello-impl', helloService); const ref: ServiceReference = reg.getReference(); diff --git a/packages/@pandino/scr/package.json b/packages/@pandino/scr/package.json index ea07f95..a94d485 100644 --- a/packages/@pandino/scr/package.json +++ b/packages/@pandino/scr/package.json @@ -13,6 +13,8 @@ }, "scripts": { "build": "rimraf dist && tsc && vite build", + "test": "vitest run", + "test:dev": "vitest", "tsc": "tsc" }, "keywords": [ diff --git a/packages/@pandino/scr/src/ComponentConfigurationImpl.ts b/packages/@pandino/scr/src/ComponentConfigurationImpl.ts index e2b0157..7bc09c3 100644 --- a/packages/@pandino/scr/src/ComponentConfigurationImpl.ts +++ b/packages/@pandino/scr/src/ComponentConfigurationImpl.ts @@ -151,7 +151,7 @@ export class ComponentConfigurationImpl implements ComponentConfiguration, } } - private deactivate(reason: DeactivationReason) { + public deactivate(reason: DeactivationReason) { if (this.isActive) { try { // 112.5.16 Deactivation diff --git a/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts b/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts index 5071b79..c4386c5 100644 --- a/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts +++ b/packages/@pandino/scr/src/ServiceComponentRuntimeImpl.ts @@ -1,6 +1,6 @@ import type { BundleContext, Logger, ServiceRegistration, ServiceUtils } from '@pandino/pandino-api'; import { SERVICE_PID } from '@pandino/pandino-api'; -import type { ConfigurationAdmin, ConfigurationListener } from '@pandino/configuration-management-api'; +import { ConfigurationAdmin, ConfigurationListener } from '@pandino/configuration-management-api'; import { CONFIGURATION_LISTENER_INTERFACE_KEY } from '@pandino/configuration-management-api'; import type { ComponentConfiguration, InternalMetaData } from '@pandino/scr-api'; import { $$PANDINO_META, COMPONENT_KEY_CONFIGURATION_PID } from '@pandino/scr-api'; @@ -43,7 +43,16 @@ export class ServiceComponentRuntimeImpl implements ServiceComponentRuntime { } } - releaseComponent(component: ComponentConfiguration): void {} + releaseComponent(config: ComponentConfiguration): void { + const ref = config.getService(); + if (ref) { + try { + (config as ComponentConfigurationImpl).deactivate('BUNDLE_STOPPED'); + } catch (e) { + this.logger.error(`Error releasing component: ${e}`); + } + } + } processComponents(): void {} diff --git a/packages/@pandino/scr/src/index.test.ts b/packages/@pandino/scr/src/index.test.ts index b1bf3f1..b607a9e 100644 --- a/packages/@pandino/scr/src/index.test.ts +++ b/packages/@pandino/scr/src/index.test.ts @@ -17,12 +17,12 @@ import { import type { Bundle, BundleContext, BundleImporter, BundleManifestHeaders, FrameworkConfigMap } from '@pandino/pandino-api'; import PMActivator from '@pandino/persistence-manager-memory'; import CMActivator from '@pandino/configuration-management'; -import { Activator as SCRActivator } from './Activator'; -import { SCR_INTERFACE_KEY } from './constants'; -import type { ComponentContext } from '@pandino/scr-api'; +import { ComponentContext, Deactivate } from '@pandino/scr-api'; import { Activate, Component } from '@pandino/scr-api'; import type { ConfigurationAdmin } from '@pandino/configuration-management-api'; import { CONFIG_ADMIN_INTERFACE_KEY } from '@pandino/configuration-management-api'; +import { Activator as SCRActivator } from './Activator'; +import { SCR_INTERFACE_KEY } from './constants'; const CMP_ONE_KEY = '@test/cmp-one'; interface CmpOne { @@ -224,6 +224,71 @@ describe('SCR', () => { }); }); + it('@Deactivate is called when SCR Bundle is uninstalled', async () => { + const [pmb, cmb, scr] = await prepareSCR(pandinoContext); + const deActivateSpy = vi.fn(); + let cmpCtx: ComponentContext | undefined; + + @Component({ name: 'test-comp-one-impl', service: CMP_ONE_KEY, property: { xOne: 1, xTwo: false } }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Deactivate() + onDeactivate(componentContext: ComponentContext) { + deActivateSpy(); + cmpCtx = componentContext; + } + } + + expect(deActivateSpy).toHaveBeenCalledTimes(0); + + await pandino.uninstallBundle(scr as any); + + expect(deActivateSpy).toHaveBeenCalledTimes(1); + expect(cmpCtx).toBeDefined(); + }); + + it('@Deactivate is called when required configuration is deleted', async () => { + const [pmb, cmb, scr] = await prepareSCR(pandinoContext); + const activateSpy = vi.fn(); + const deActivateSpy = vi.fn(); + const configAdminRef = pandinoContext.getServiceReference(CONFIG_ADMIN_INTERFACE_KEY)!; + const configAdmin = pandinoContext.getService(configAdminRef)!; + const pid = 'custom-pid'; + + const config = configAdmin.getConfiguration(pid); + config.update({ + yolo: 'hello', + }); + + @Component({ name: 'test-comp-one-impl', configurationPolicy: 'REQUIRE', service: CMP_ONE_KEY, configurationPid: pid }) + class OneImpl implements CmpOne { + hello(inp: string): string { + return inp.toUpperCase(); + } + + @Activate() + onActivate(componentContext: ComponentContext, bundleContext: BundleContext, properties?: ServiceProperties) { + activateSpy(); + } + + @Deactivate() + onDeactivate() { + deActivateSpy(); + } + } + + expect(activateSpy).toHaveBeenCalledTimes(1); + expect(deActivateSpy).toHaveBeenCalledTimes(0); + + config.delete(); + const asd = configAdmin.listConfigurations(); + + expect(deActivateSpy).toHaveBeenCalledTimes(1); + }); + async function installDepBundles(ctx: BundleContext): Promise { const pmb = await ctx.installBundle(pmHeaders()); const cmb = await ctx.installBundle(cmHeaders()); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6755f8a..24f6f01 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: '@pandino/persistence-manager-api': specifier: workspace:^0.8.31 version: link:../persistence-manager-api + '@pandino/persistence-manager-memory': + specifier: workspace:^0.8.31 + version: link:../persistence-manager-memory '@pandino/rollup-plugin-generate-manifest': specifier: workspace:^0.8.31 version: link:../rollup-plugin-generate-manifest