qwen_2.5_model / src /modules /integration /plugin-instance.service.spec.ts
Muhammad Noman
Deploy OpenWA to Hugging Face Spaces
46252cd
Raw
History Blame Contribute Delete
7.67 kB
import { DataSource } from 'typeorm';
import { PluginInstance } from './entities/plugin-instance.entity';
import { PluginInstanceService, InstanceExistsError } from './plugin-instance.service';
import { AddIntegrationFabric1781900000000 } from '../../database/migrations/1781900000000-AddIntegrationFabric';
import type { PluginConfigSchema } from '../../core/plugins/plugin.interfaces';
describe('PluginInstanceService', () => {
let ds: DataSource;
let service: PluginInstanceService;
beforeEach(async () => {
ds = new DataSource({ type: 'better-sqlite3', database: ':memory:', entities: [PluginInstance], migrations: [] });
await ds.initialize();
const runner = ds.createQueryRunner();
await new AddIntegrationFabric1781900000000().up(runner);
await runner.release();
service = new PluginInstanceService(ds.getRepository(PluginInstance));
});
afterEach(async () => {
if (ds.isInitialized) await ds.destroy();
});
it('mints a 64-hex-char secret and stores a composite id', async () => {
const inst = await service.mint('chatwoot', 'acct1', { sessionScope: 'sess-1' });
expect(inst.id).toBe('chatwoot:acct1');
expect(inst.secret).toMatch(/^[0-9a-f]{64}$/);
});
it('masks the secret on the operator-facing view', async () => {
const inst = await service.mint('chatwoot', 'acct1', {});
expect(service.maskedView(inst).secret).toBe('***');
});
it('masks secret:true config fields (apiToken) on masked reads', () => {
const schema = {
type: 'object',
properties: { apiToken: { type: 'string', secret: true }, accountId: { type: 'number' } },
} as PluginConfigSchema;
const inst = {
id: 'p:i',
secret: 'x',
config: { apiToken: 'live-token', accountId: 3 },
} as unknown as PluginInstance;
const masked = service.maskedView(inst, schema);
const config = masked.config as Record<string, unknown>;
expect(masked.secret).toBe('***');
expect(config.apiToken).toBe('***');
expect(config.accountId).toBe(3);
});
it('masks the ENTIRE config when the schema is unavailable, e.g. the plugin is unloaded (fail-closed)', () => {
const inst = {
id: 'p:i',
secret: 'x',
config: { apiToken: 'live-token', accountId: 3 },
} as unknown as PluginInstance;
const config = service.maskedView(inst, undefined).config as Record<string, unknown>;
expect(config.apiToken).toBe('***');
expect(config.accountId).toBe('***');
});
it('resolves an existing instance and returns null for an unknown one', async () => {
await service.mint('chatwoot', 'acct1', {});
expect((await service.resolve('chatwoot', 'acct1'))?.id).toBe('chatwoot:acct1');
expect(await service.resolve('chatwoot', 'nope')).toBeNull();
});
it('accepts a valid operator secret, rejects short/empty, else auto-generates', async () => {
const ok = await service.create('chatwoot-adapter', 'a1', { secret: 'cw-secret-16chars!' });
expect(ok.secret).toBe('cw-secret-16chars!');
await expect(service.create('chatwoot-adapter', 'a2', { secret: ' ' })).rejects.toThrow(/secret/i);
await expect(service.create('chatwoot-adapter', 'a3', { secret: 'short' })).rejects.toThrow(/16/);
const gen = await service.create('chatwoot-adapter', 'a4', {});
expect(gen.secret).toMatch(/^[0-9a-f]{64}$/);
});
it('masks a NESTED secret:true config field on masked reads (recursive redaction)', () => {
const schema = {
type: 'object',
properties: {
provider: {
type: 'object',
properties: { apiToken: { type: 'string', secret: true }, region: { type: 'string' } },
},
},
} as PluginConfigSchema;
const inst = {
id: 'p:i',
secret: 'x',
config: { provider: { apiToken: 'live-token', region: 'us' } },
} as unknown as PluginInstance;
const config = service.maskedView(inst, schema).config as { provider: Record<string, unknown> };
expect(config.provider.apiToken).toBe('***');
expect(config.provider.region).toBe('us');
});
it('update restores a masked (sentinel) secret to the stored value instead of persisting "***"', async () => {
const schema = {
type: 'object',
properties: { apiToken: { type: 'string', secret: true }, accountId: { type: 'number' } },
} as PluginConfigSchema;
await service.create('chatwoot', 'acct1', { config: { apiToken: 'real-token', accountId: 1 } });
// Dashboard round-trips the masked config back with an edited non-secret field.
const updated = await service.update('chatwoot', 'acct1', { config: { apiToken: '***', accountId: 2 } }, schema);
expect(updated?.config).toEqual({ apiToken: 'real-token', accountId: 2 });
});
});
describe('PluginInstanceService provisioning', () => {
let ds: DataSource;
let service: PluginInstanceService;
beforeEach(async () => {
ds = new DataSource({ type: 'better-sqlite3', database: ':memory:', entities: [PluginInstance], migrations: [] });
await ds.initialize();
const runner = ds.createQueryRunner();
await new AddIntegrationFabric1781900000000().up(runner);
await runner.release();
service = new PluginInstanceService(ds.getRepository(PluginInstance));
});
afterEach(async () => {
if (ds.isInitialized) await ds.destroy();
});
it('create mints a new instance and rejects a duplicate with InstanceExistsError', async () => {
const inst = await service.create('chatwoot', 'acct1', { sessionScope: 'sess-1' });
expect(inst.id).toBe('chatwoot:acct1');
expect(inst.secret).toMatch(/^[0-9a-f]{64}$/);
await expect(service.create('chatwoot', 'acct1', {})).rejects.toBeInstanceOf(InstanceExistsError);
});
it('list returns all instances for a plugin', async () => {
await service.create('chatwoot', 'acct1', {});
await service.create('chatwoot', 'acct2', {});
await service.create('other', 'x', {});
const list = await service.list('chatwoot');
expect(list.map(i => i.instanceId).sort()).toEqual(['acct1', 'acct2']);
});
it('regenerateSecret replaces the secret with a new value', async () => {
const created = await service.create('chatwoot', 'acct1', {});
const rotated = await service.regenerateSecret('chatwoot', 'acct1');
expect(rotated.secret).toMatch(/^[0-9a-f]{64}$/);
expect(rotated.secret).not.toBe(created.secret);
});
it('setEnabled toggles enabled; update patches scope/config; remove deletes', async () => {
await service.create('chatwoot', 'acct1', { sessionScope: 'a' });
expect((await service.setEnabled('chatwoot', 'acct1', false))?.enabled).toBe(false);
const patched = await service.update('chatwoot', 'acct1', { sessionScope: 'b', config: { k: 1 } });
expect(patched?.sessionScope).toBe('b');
expect(patched?.config).toEqual({ k: 1 });
expect(await service.remove('chatwoot', 'acct1')).toBe(true);
expect(await service.resolve('chatwoot', 'acct1')).toBeNull();
expect(await service.remove('chatwoot', 'acct1')).toBe(false);
});
it('normalizes an empty sessionScope to null (all-sessions) on mint/create/update, never a literal ""', async () => {
// '' would break outbound send (falsy sessionId) and be unrecoverable from the UI; store null instead.
const minted = await service.mint('chatwoot', 'm1', { sessionScope: '' });
expect(minted.sessionScope).toBeNull();
const created = await service.create('chatwoot', 'acct1', { sessionScope: '' });
expect(created.sessionScope).toBeNull();
const patched = await service.update('chatwoot', 'acct1', { sessionScope: '' });
expect(patched?.sessionScope).toBeNull();
});
});