mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 19:21:24 +01:00
306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
import { Hub } from '../../src/types'
|
|
import { processError } from '../../src/utils/db/error'
|
|
import { closeHub, createHub } from '../../src/utils/db/hub'
|
|
import { loadPlugin } from '../../src/worker/plugins/loadPlugin'
|
|
import { setupPlugins } from '../../src/worker/plugins/setup'
|
|
import { LazyPluginVM } from '../../src/worker/vm/lazy'
|
|
import {
|
|
commonOrganizationId,
|
|
mockPluginSourceCode,
|
|
mockPluginTempFolder,
|
|
mockPluginWithSourceFiles,
|
|
plugin60,
|
|
pluginAttachment1,
|
|
pluginConfig39,
|
|
} from '../helpers/plugins'
|
|
import { resetTestDatabase } from '../helpers/sql'
|
|
import { getPluginAttachmentRows, getPluginConfigRows, getPluginRows, setPluginCapabilities } from '../helpers/sqlMock'
|
|
|
|
jest.mock('../../src/utils/db/sql')
|
|
jest.mock('../../src/utils/logger')
|
|
jest.mock('../../src/utils/db/error')
|
|
jest.mock('../../src/worker/plugins/loadPlugin', () => {
|
|
const { loadPlugin } = jest.requireActual('../../src/worker/plugins/loadPlugin')
|
|
return { loadPlugin: jest.fn().mockImplementation(loadPlugin) }
|
|
})
|
|
jest.setTimeout(20_000)
|
|
|
|
describe('plugins', () => {
|
|
let hub: Hub
|
|
|
|
beforeEach(async () => {
|
|
hub = await createHub({ LOG_LEVEL: 'info' })
|
|
console.warn = jest.fn() as any
|
|
await resetTestDatabase()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await closeHub(hub)
|
|
})
|
|
|
|
test('stateless plugins', async () => {
|
|
const plugin = { ...plugin60, is_stateless: true }
|
|
getPluginRows.mockReturnValueOnce([plugin])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39, { ...pluginConfig39, id: 40, team_id: 1 }])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
expect(getPluginRows).toHaveBeenCalled()
|
|
expect(getPluginAttachmentRows).toHaveBeenCalled()
|
|
expect(getPluginConfigRows).toHaveBeenCalled()
|
|
|
|
expect(Array.from(pluginConfigs.keys())).toEqual([39, 40])
|
|
|
|
const pluginConfigTeam1 = pluginConfigs.get(40)!
|
|
const pluginConfigTeam2 = pluginConfigs.get(39)!
|
|
|
|
expect(pluginConfigTeam1.plugin).toEqual(plugin)
|
|
expect(pluginConfigTeam2.plugin).toEqual(plugin)
|
|
|
|
expect(pluginConfigTeam1.instance).toBeTruthy()
|
|
expect(pluginConfigTeam2.instance).toBeTruthy()
|
|
|
|
expect(pluginConfigTeam1.instance).toEqual(pluginConfigTeam2.instance)
|
|
})
|
|
|
|
test('local plugin with broken plugin.json does not do much', async () => {
|
|
// silence some spam
|
|
console.log = jest.fn()
|
|
console.error = jest.fn()
|
|
|
|
const [plugin, unlink] = mockPluginTempFolder(
|
|
`function processEvent (event, meta) { event.properties.processed = true; return event }`,
|
|
'{ broken: "plugin.json" -=- '
|
|
)
|
|
getPluginRows.mockReturnValueOnce([plugin])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
expect(processError).toHaveBeenCalledWith(
|
|
hub,
|
|
pluginConfigs.get(39)!,
|
|
expect.stringContaining('Could not load "plugin.json" for plugin ')
|
|
)
|
|
|
|
unlink()
|
|
})
|
|
|
|
test('plugin with http urls must have source files', async () => {
|
|
// silence some spam
|
|
console.log = jest.fn()
|
|
console.error = jest.fn()
|
|
|
|
getPluginRows.mockReturnValueOnce([{ ...plugin60, source__index_ts: undefined }])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
expect(pluginConfigs.get(39)!.plugin!.url).toContain('https://')
|
|
expect(processError).toHaveBeenCalledWith(
|
|
hub,
|
|
pluginConfigs.get(39)!,
|
|
`Could not load source code for plugin test-maxmind-plugin ID 60 (organization ID ${commonOrganizationId}). Tried: index.js`
|
|
)
|
|
})
|
|
|
|
test('plugin with source files loads capabilities', async () => {
|
|
getPluginRows.mockReturnValueOnce([
|
|
mockPluginWithSourceFiles(`
|
|
function setupPlugin (meta) { meta.global.key = 'value' }
|
|
function processEvent (event, meta) { event.properties={"x": 1}; return event }
|
|
`),
|
|
])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
const pluginConfig = pluginConfigs.get(39)!
|
|
|
|
await (pluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
|
|
expect(pluginConfig.plugin!.capabilities!.methods!.sort()).toEqual(['processEvent', 'setupPlugin'])
|
|
})
|
|
|
|
test('plugin with source files loads all capabilities, no random caps', async () => {
|
|
getPluginRows.mockReturnValueOnce([
|
|
mockPluginWithSourceFiles(`
|
|
export function processEvent (event, meta) { event.properties={"x": 1}; return event }
|
|
export function randomFunction (event, meta) { return event}
|
|
export function onEvent (event, meta) { return event }
|
|
`),
|
|
])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
const pluginConfig = pluginConfigs.get(39)!
|
|
|
|
await (pluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
|
|
expect(pluginConfig.plugin!.capabilities!.methods!.sort()).toEqual(['onEvent', 'processEvent'])
|
|
})
|
|
|
|
test('plugin with source file loads capabilities', async () => {
|
|
const [plugin, unlink] = mockPluginTempFolder(`
|
|
function processEvent (event, meta) { event.properties={"x": 1}; return event }
|
|
function randomFunction (event, meta) { return event}
|
|
function onEvent (event, meta) { return event }
|
|
`)
|
|
|
|
getPluginRows.mockReturnValueOnce([plugin])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
const pluginConfig = pluginConfigs.get(39)!
|
|
|
|
await (pluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
|
|
expect(pluginConfig.plugin!.capabilities!.methods!.sort()).toEqual(['onEvent', 'processEvent'])
|
|
|
|
unlink()
|
|
})
|
|
|
|
test('plugin with source code loads capabilities', async () => {
|
|
getPluginRows.mockReturnValueOnce([
|
|
{
|
|
...mockPluginSourceCode(),
|
|
source__index_ts: `
|
|
function processEvent (event, meta) { event.properties={"x": 1}; return event }
|
|
function randomFunction (event, meta) { return event}
|
|
function onEvent (event, meta) { return event }`,
|
|
},
|
|
])
|
|
getPluginConfigRows.mockReturnValueOnce([pluginConfig39])
|
|
getPluginAttachmentRows.mockReturnValueOnce([pluginAttachment1])
|
|
|
|
await setupPlugins(hub)
|
|
const { pluginConfigs } = hub
|
|
|
|
const pluginConfig = pluginConfigs.get(39)!
|
|
|
|
await (pluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
|
|
expect(pluginConfig.plugin!.capabilities!.methods!.sort()).toEqual(['onEvent', 'processEvent'])
|
|
})
|
|
|
|
test('reloading plugins after config changes', async () => {
|
|
const makePlugin = (id: number, updated_at = '2020-11-02'): any => ({
|
|
...plugin60,
|
|
id,
|
|
plugin_type: 'source',
|
|
source: setOrderCode(60),
|
|
updated_at,
|
|
})
|
|
const makeConfig = (plugin_id: number, id: number, order: number, updated_at = '2020-11-02'): any => ({
|
|
...pluginConfig39,
|
|
plugin_id,
|
|
id,
|
|
order,
|
|
updated_at,
|
|
})
|
|
|
|
const setOrderCode = (id: number) => {
|
|
return `
|
|
function processEvent(event) {
|
|
if (!event.properties.plugins) { event.properties.plugins = [] }
|
|
event.properties.plugins.push(${id})
|
|
return event
|
|
}
|
|
`
|
|
}
|
|
|
|
getPluginRows.mockReturnValue([makePlugin(60), makePlugin(61), makePlugin(62), makePlugin(63)])
|
|
getPluginAttachmentRows.mockReturnValue([])
|
|
getPluginConfigRows.mockReturnValue([
|
|
makeConfig(60, 39, 0),
|
|
makeConfig(61, 41, 1),
|
|
makeConfig(62, 40, 3),
|
|
makeConfig(63, 42, 2),
|
|
])
|
|
|
|
await setupPlugins(hub)
|
|
|
|
expect(loadPlugin).toHaveBeenCalledTimes(4)
|
|
expect(Array.from(hub.plugins.keys())).toEqual(expect.arrayContaining([60, 61, 62, 63]))
|
|
expect(Array.from(hub.pluginConfigs.keys())).toEqual(expect.arrayContaining([39, 40, 41, 42]))
|
|
|
|
expect(hub.pluginConfigsPerTeam.get(pluginConfig39.team_id)?.map((c) => [c.id, c.order])).toEqual([
|
|
[39, 0],
|
|
[41, 1],
|
|
[42, 2],
|
|
[40, 3],
|
|
])
|
|
|
|
getPluginRows.mockReturnValue([makePlugin(60), makePlugin(61), makePlugin(63, '2021-02-02'), makePlugin(64)])
|
|
getPluginAttachmentRows.mockReturnValue([])
|
|
getPluginConfigRows.mockReturnValue([
|
|
makeConfig(60, 39, 0),
|
|
makeConfig(61, 41, 3, '2021-02-02'),
|
|
makeConfig(63, 42, 2),
|
|
makeConfig(64, 43, 1),
|
|
])
|
|
|
|
await setupPlugins(hub)
|
|
|
|
expect(loadPlugin).toHaveBeenCalledTimes(4 + 3)
|
|
expect(Array.from(hub.plugins.keys())).toEqual(expect.arrayContaining([60, 61, 63, 64]))
|
|
expect(Array.from(hub.pluginConfigs.keys())).toEqual(expect.arrayContaining([39, 41, 42, 43]))
|
|
|
|
expect(hub.pluginConfigsPerTeam.get(pluginConfig39.team_id)?.map((c) => [c.id, c.order])).toEqual([
|
|
[39, 0],
|
|
[43, 1],
|
|
[42, 2],
|
|
[41, 3],
|
|
])
|
|
})
|
|
|
|
test("capabilities don't reload without changes", async () => {
|
|
getPluginRows.mockReturnValueOnce([{ ...plugin60 }]).mockReturnValueOnce([
|
|
{
|
|
...plugin60,
|
|
capabilities: { methods: ['processEvent'] },
|
|
},
|
|
]) // updated in DB via first `setPluginCapabilities` call.
|
|
getPluginAttachmentRows.mockReturnValue([pluginAttachment1])
|
|
getPluginConfigRows.mockReturnValue([pluginConfig39])
|
|
|
|
await setupPlugins(hub)
|
|
const pluginConfig = hub.pluginConfigs.get(39)!
|
|
|
|
await (pluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
expect(setPluginCapabilities.mock.calls.length).toBe(1)
|
|
|
|
pluginConfig.updated_at = new Date().toISOString()
|
|
// config is changed, but capabilities haven't changed
|
|
|
|
await setupPlugins(hub)
|
|
const newPluginConfig = hub.pluginConfigs.get(39)!
|
|
|
|
await (newPluginConfig.instance as LazyPluginVM)?.resolveInternalVm
|
|
// async loading of capabilities
|
|
|
|
expect(newPluginConfig.plugin).not.toBe(pluginConfig.plugin)
|
|
expect(setPluginCapabilities.mock.calls.length).toBe(1)
|
|
expect(newPluginConfig.plugin!.capabilities).toEqual(pluginConfig.plugin!.capabilities)
|
|
})
|
|
})
|