Spaces:
Runtime error
Runtime error
File size: 7,970 Bytes
46252cd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | import { IntegrationInstanceController } from './integration-instance.controller';
import { PluginInstanceService } from './plugin-instance.service';
import { PluginLoaderService } from '../../core/plugins/plugin-loader.service';
import { AuditService } from '../audit/audit.service';
import { ScopeBindingService } from './scope-binding.service';
// The provisioning bridge is what makes a minted instance's config reach the ingress worker: on
// create/patch it mirrors the instance config into the plugin's per-session config and activates the
// bound session; on delete it clears both. dispatchWebhookForInstance then resolves it as ctx.config.
describe('IntegrationInstanceController provisioning bridge', () => {
function build() {
const setPluginSessionConfig = jest.fn();
const setPluginSessions = jest.fn();
const updatePluginConfig = jest.fn();
const loader = {
getPlugin: jest.fn().mockReturnValue({
manifest: { id: 'chatwoot-adapter', ingress: [{ route: 'chatwoot' }], permissions: ['webhook:ingress'] },
activeSessions: [],
}),
setPluginSessionConfig,
setPluginSessions,
updatePluginConfig,
} as unknown as PluginLoaderService;
const audit = { logInfo: jest.fn() } as unknown as AuditService;
return { loader, audit, setPluginSessionConfig, setPluginSessions, updatePluginConfig };
}
it('bridges instance config into per-session config + activates the session on create', async () => {
const { loader, audit, setPluginSessionConfig, setPluginSessions } = build();
const instances = {
create: jest.fn().mockResolvedValue({
id: 'chatwoot-adapter:acct1',
pluginId: 'chatwoot-adapter',
instanceId: 'acct1',
sessionScope: 'sess-1',
secret: 's',
verifyToken: null,
config: { baseUrl: 'https://x' },
enabled: true,
createdAt: new Date(0),
updatedAt: new Date(0),
}),
} as unknown as PluginInstanceService;
const controller = new IntegrationInstanceController(
instances,
loader,
audit,
new ScopeBindingService(instances, loader, audit),
);
await controller.create('chatwoot-adapter', {
instanceId: 'acct1',
sessionScope: 'sess-1',
config: { baseUrl: 'https://x' },
});
expect(setPluginSessionConfig).toHaveBeenCalledWith('chatwoot-adapter', 'sess-1', { baseUrl: 'https://x' });
expect(setPluginSessions).toHaveBeenCalledWith('chatwoot-adapter', ['sess-1']);
});
it('deactivates the session + clears its config when the instance is disabled (PATCH enabled:false)', async () => {
const { loader, audit, setPluginSessionConfig, setPluginSessions } = build();
(loader.getPlugin as jest.Mock).mockReturnValue({
manifest: { id: 'chatwoot-adapter', ingress: [{ route: 'chatwoot' }], permissions: ['webhook:ingress'] },
activeSessions: ['sess-1'],
});
const base = {
pluginId: 'chatwoot-adapter',
instanceId: 'acct1',
sessionScope: 'sess-1',
config: { baseUrl: 'https://x' },
};
const instances = {
resolve: jest.fn().mockResolvedValue({ ...base, enabled: true }),
setEnabled: jest.fn().mockResolvedValue({ ...base, enabled: false }),
update: jest.fn(),
maskedView: (i: unknown) => i,
} as unknown as PluginInstanceService;
const controller = new IntegrationInstanceController(
instances,
loader,
audit,
new ScopeBindingService(instances, loader, audit),
);
await controller.patch('chatwoot-adapter', 'acct1', { enabled: false });
// A disabled instance must stop firing outbound: session cleared + removed from activeSessions.
expect(setPluginSessionConfig).toHaveBeenCalledWith('chatwoot-adapter', 'sess-1', {});
expect(setPluginSessions).toHaveBeenCalledWith('chatwoot-adapter', []);
});
it('retires the "*" activation when a wildcard-scope instance is disabled and no other wildcard remains', async () => {
const { loader, audit, setPluginSessions } = build();
(loader.getPlugin as jest.Mock).mockReturnValue({
manifest: { id: 'chatwoot-adapter', ingress: [{ route: 'chatwoot' }], permissions: ['webhook:ingress'] },
activeSessions: ['*'],
});
const base = { pluginId: 'chatwoot-adapter', instanceId: 'acct1', sessionScope: '*', config: {} };
const instances = {
resolve: jest.fn().mockResolvedValue({ ...base, enabled: true }),
setEnabled: jest.fn().mockResolvedValue({ ...base, enabled: false }),
update: jest.fn(),
list: jest.fn().mockResolvedValue([{ ...base, enabled: false }]), // only this one, now disabled
maskedView: (i: unknown) => i,
} as unknown as PluginInstanceService;
const controller = new IntegrationInstanceController(
instances,
loader,
audit,
new ScopeBindingService(instances, loader, audit),
);
await controller.patch('chatwoot-adapter', 'acct1', { enabled: false });
// Previously a no-op: a disabled wildcard instance kept firing on every session. Now '*' is retired.
expect(setPluginSessions).toHaveBeenCalledWith('chatwoot-adapter', []);
});
it('keeps "*" active when another enabled wildcard instance remains', async () => {
const { loader, audit, setPluginSessions } = build();
(loader.getPlugin as jest.Mock).mockReturnValue({
manifest: { id: 'chatwoot-adapter', ingress: [{ route: 'chatwoot' }], permissions: ['webhook:ingress'] },
activeSessions: ['*'],
});
const base = { pluginId: 'chatwoot-adapter', instanceId: 'acct1', sessionScope: '*', config: {} };
const instances = {
resolve: jest.fn().mockResolvedValue({ ...base, enabled: true }),
setEnabled: jest.fn().mockResolvedValue({ ...base, enabled: false }),
update: jest.fn(),
list: jest.fn().mockResolvedValue([
{ ...base, enabled: false },
{ pluginId: 'chatwoot-adapter', instanceId: 'acct2', sessionScope: '*', config: {}, enabled: true },
]),
maskedView: (i: unknown) => i,
} as unknown as PluginInstanceService;
const controller = new IntegrationInstanceController(
instances,
loader,
audit,
new ScopeBindingService(instances, loader, audit),
);
await controller.patch('chatwoot-adapter', 'acct1', { enabled: false });
// A second wildcard instance is still enabled → '*' must NOT be retired.
expect(setPluginSessions).not.toHaveBeenCalledWith('chatwoot-adapter', []);
});
it('tears down the OLD scope when the bound session changes (PATCH sessionScope)', async () => {
const { loader, audit, setPluginSessionConfig } = build();
(loader.getPlugin as jest.Mock).mockReturnValue({
manifest: { id: 'chatwoot-adapter', ingress: [{ route: 'chatwoot' }], permissions: ['webhook:ingress'] },
activeSessions: ['sess-1'],
});
const instances = {
resolve: jest.fn().mockResolvedValue({
pluginId: 'chatwoot-adapter',
instanceId: 'acct1',
sessionScope: 'sess-1',
config: { baseUrl: 'https://x' },
enabled: true,
}),
setEnabled: jest.fn(),
update: jest.fn().mockResolvedValue({
pluginId: 'chatwoot-adapter',
instanceId: 'acct1',
sessionScope: 'sess-2',
config: { baseUrl: 'https://y' },
enabled: true,
}),
maskedView: (i: unknown) => i,
} as unknown as PluginInstanceService;
const controller = new IntegrationInstanceController(
instances,
loader,
audit,
new ScopeBindingService(instances, loader, audit),
);
await controller.patch('chatwoot-adapter', 'acct1', { sessionScope: 'sess-2' });
expect(setPluginSessionConfig).toHaveBeenCalledWith('chatwoot-adapter', 'sess-1', {}); // old scope torn down
expect(setPluginSessionConfig).toHaveBeenCalledWith('chatwoot-adapter', 'sess-2', { baseUrl: 'https://y' }); // new bound
});
});
|