feat: plugins v1.5
This commit is contained in:
@@ -28,22 +28,22 @@ test.describe('Plugin API multi-user runtime', () => {
|
||||
test('runs chat, embed, soundboard, and profile APIs between two users', async ({ createClient }) => {
|
||||
const scenario = await createPluginApiScenario(createClient);
|
||||
|
||||
await test.step('Install and activate the plugin for Bob as the embed/soundboard receiver', async () => {
|
||||
await installGrantAndActivatePlugin(scenario.bob.page);
|
||||
await closeSettingsModal(scenario.bob.page);
|
||||
await expect(soundboardComposerButton(scenario.bob.page)).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.bob.page.getByText(SOUND_BOARD_TEXT, { exact: true })).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.bob.page.getByTestId('e2e-plugin-owned-dom')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
|
||||
});
|
||||
|
||||
await test.step('Install and activate the plugin for Alice as the API driver', async () => {
|
||||
await installGrantAndActivatePlugin(scenario.alice.page);
|
||||
await test.step('Install the server plugin as Alice', async () => {
|
||||
await installGrantAndActivatePlugin(scenario.alice.page, true);
|
||||
await closeSettingsModal(scenario.alice.page);
|
||||
await expect(soundboardComposerButton(scenario.alice.page)).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.alice.page.getByText(SOUND_BOARD_TEXT, { exact: true })).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.alice.page.getByTestId('e2e-plugin-owned-dom')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
|
||||
});
|
||||
|
||||
await test.step('Activate the server plugin for Bob as the embed/soundboard receiver', async () => {
|
||||
await installGrantAndActivatePlugin(scenario.bob.page, false);
|
||||
await closeSettingsModal(scenario.bob.page);
|
||||
await expect(soundboardComposerButton(scenario.bob.page)).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.bob.page.getByText(SOUND_BOARD_TEXT, { exact: true })).toBeVisible({ timeout: 20_000 });
|
||||
await expect(scenario.bob.page.getByTestId('e2e-plugin-owned-dom')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
|
||||
});
|
||||
|
||||
await test.step('Alice opens the plugin soundboard modal and plays a sound to voice', async () => {
|
||||
await soundboardComposerButton(scenario.alice.page).click();
|
||||
await expect(scenario.alice.page.getByRole('dialog', { name: SOUND_BOARD_LABEL })).toBeVisible({ timeout: 20_000 });
|
||||
@@ -140,15 +140,21 @@ async function registerUser(page: Page, username: string, displayName: string):
|
||||
await expect(page).toHaveURL(/\/search/, { timeout: 30_000 });
|
||||
}
|
||||
|
||||
async function installGrantAndActivatePlugin(page: Page): Promise<void> {
|
||||
async function installGrantAndActivatePlugin(page: Page, installFromStore: boolean): Promise<void> {
|
||||
await page.getByRole('button', { name: 'Plugins' }).click();
|
||||
await expect(page).toHaveURL(/\/plugin-store/, { timeout: 20_000 });
|
||||
await expect(page.getByTestId('plugin-store-page')).toBeVisible({ timeout: 20_000 });
|
||||
await page.getByPlaceholder('https://example.com/plugins.json').fill(PLUGIN_SOURCE_URL);
|
||||
await page.getByRole('button', { name: 'Add Source' }).click();
|
||||
await expect(page.getByRole('heading', { name: PLUGIN_TITLE })).toBeVisible({ timeout: 20_000 });
|
||||
await page.getByRole('button', { exact: true, name: 'Install' }).click();
|
||||
await expect(page.locator('article', { hasText: PLUGIN_TITLE }).getByText('Installed')).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
if (installFromStore) {
|
||||
await page.getByLabel('Plugin source manifest URL').fill(PLUGIN_SOURCE_URL);
|
||||
await page.getByRole('button', { name: 'Add Source' }).click();
|
||||
await expect(page.getByRole('heading', { name: PLUGIN_TITLE })).toBeVisible({ timeout: 20_000 });
|
||||
await page.getByRole('button', { exact: true, name: /^(Install|Install to Server)$/ }).click();
|
||||
await expect(page.getByRole('dialog', { name: PLUGIN_TITLE })).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByRole('button', { name: 'Install and Activate' }).click();
|
||||
await expect(page.locator('article', { hasText: PLUGIN_TITLE }).getByText('Installed')).toBeVisible({ timeout: 20_000 });
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Manage Plugins' }).click();
|
||||
await expect(page.getByTestId('plugin-manager')).toBeVisible({ timeout: 20_000 });
|
||||
await expect(page.locator('article', { hasText: PLUGIN_TITLE })).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
@@ -30,20 +30,25 @@ test.describe('Plugin manager UI', () => {
|
||||
});
|
||||
|
||||
await test.step('Install fixture plugin from source manifest', async () => {
|
||||
await page.getByPlaceholder('https://example.com/plugins.json').fill('http://localhost:4200/plugins/e2e-plugin-source.json');
|
||||
await page.getByLabel('Plugin source manifest URL').fill('http://localhost:4200/plugins/e2e-plugin-source.json');
|
||||
await page.getByRole('button', { name: 'Add Source' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'E2E All API Plugin' })).toBeVisible({ timeout: 15_000 });
|
||||
await page.getByRole('button', { name: 'Readme' }).click();
|
||||
await expect(page.getByText('Fixture plugin for Playwright coverage.')).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByRole('button', { exact: true, name: 'Install' }).click();
|
||||
await page.getByRole('button', { exact: true, name: /^(Install|Install to Server)$/ }).click();
|
||||
const installDialog = page.getByRole('dialog', { name: 'E2E All API Plugin' });
|
||||
|
||||
await expect(installDialog).toBeVisible({ timeout: 10_000 });
|
||||
await expect(installDialog.getByText('Install to server', { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Install and Activate' }).click();
|
||||
await expect(page.locator('article', { hasText: 'E2E All API Plugin' }).getByText('Installed')).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
await test.step('Open plugin manager from the store page', async () => {
|
||||
await page.getByRole('button', { name: 'Manage Plugins' }).click();
|
||||
await expect(page.getByTestId('plugin-manager')).toBeVisible({ timeout: 10_000 });
|
||||
await expect(page.getByTestId('plugin-manager').getByRole('heading', { name: 'Plugins' })).toBeVisible();
|
||||
await expect(page.getByText('Development Plugin')).toBeVisible();
|
||||
await expect(page.getByTestId('plugin-manager').getByRole('heading', { name: 'Server plugins' })).toBeVisible();
|
||||
await expect(page.getByText('E2E All API Plugin')).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Grant capabilities and activate runtime', async () => {
|
||||
|
||||
@@ -34,21 +34,6 @@ interface PluginEventDefinitionResponse {
|
||||
};
|
||||
}
|
||||
|
||||
interface PluginDataResponse {
|
||||
record: {
|
||||
key: string;
|
||||
ownerId?: string;
|
||||
pluginId: string;
|
||||
schemaVersion: number;
|
||||
scope: string;
|
||||
value: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
interface PluginDataListResponse {
|
||||
records: PluginDataResponse['record'][];
|
||||
}
|
||||
|
||||
interface PluginSnapshotResponse {
|
||||
eventDefinitions: PluginEventDefinitionResponse['eventDefinition'][];
|
||||
requirements: PluginRequirementResponse['requirement'][];
|
||||
@@ -136,8 +121,8 @@ test.describe('Plugin support API', () => {
|
||||
expect(snapshot.eventDefinitions.map((entry) => entry.eventName).sort()).toEqual([TEST_PLUGIN_P2P_EVENT, TEST_PLUGIN_RELAY_EVENT]);
|
||||
});
|
||||
|
||||
await test.step('Plugin data API stores, lists, and deletes server scoped data', async () => {
|
||||
const stored = await expectJson<PluginDataResponse>(await request.put(`${pluginsApi}/${TEST_PLUGIN_ID}/data/settings`, {
|
||||
await test.step('Plugin data API refuses arbitrary server persistence', async () => {
|
||||
const stored = await expectJson<{ errorCode: string }>(await request.put(`${pluginsApi}/${TEST_PLUGIN_ID}/data/settings`, {
|
||||
data: {
|
||||
actorUserId: OWNER_USER_ID,
|
||||
schemaVersion: 1,
|
||||
@@ -147,49 +132,28 @@ test.describe('Plugin support API', () => {
|
||||
pluginVersion: manifest.version
|
||||
}
|
||||
}
|
||||
}));
|
||||
}), 410);
|
||||
|
||||
expect(stored.record).toEqual(expect.objectContaining({
|
||||
key: 'settings',
|
||||
pluginId: TEST_PLUGIN_ID,
|
||||
schemaVersion: 1,
|
||||
scope: 'server',
|
||||
value: {
|
||||
enabled: true,
|
||||
pluginVersion: manifest.version
|
||||
}
|
||||
}));
|
||||
expect(stored.errorCode).toBe('PLUGIN_DATA_DISABLED');
|
||||
|
||||
const listed = await expectJson<PluginDataListResponse>(await request.get(`${pluginsApi}/${TEST_PLUGIN_ID}/data`, {
|
||||
const listed = await expectJson<{ errorCode: string }>(await request.get(`${pluginsApi}/${TEST_PLUGIN_ID}/data`, {
|
||||
params: {
|
||||
key: 'settings',
|
||||
scope: 'server',
|
||||
userId: OWNER_USER_ID
|
||||
}
|
||||
}));
|
||||
}), 410);
|
||||
|
||||
expect(listed.records).toHaveLength(1);
|
||||
expect(listed.records[0]?.value).toEqual({
|
||||
enabled: true,
|
||||
pluginVersion: manifest.version
|
||||
});
|
||||
expect(listed.errorCode).toBe('PLUGIN_DATA_DISABLED');
|
||||
|
||||
await expectJson<{ ok: boolean }>(await request.delete(`${pluginsApi}/${TEST_PLUGIN_ID}/data/settings`, {
|
||||
const afterDelete = await expectJson<{ errorCode: string }>(await request.delete(`${pluginsApi}/${TEST_PLUGIN_ID}/data/settings`, {
|
||||
data: {
|
||||
actorUserId: OWNER_USER_ID,
|
||||
scope: 'server'
|
||||
}
|
||||
}));
|
||||
}), 410);
|
||||
|
||||
const afterDelete = await expectJson<PluginDataListResponse>(await request.get(`${pluginsApi}/${TEST_PLUGIN_ID}/data`, {
|
||||
params: {
|
||||
key: 'settings',
|
||||
scope: 'server',
|
||||
userId: OWNER_USER_ID
|
||||
}
|
||||
}));
|
||||
|
||||
expect(afterDelete.records).toEqual([]);
|
||||
expect(afterDelete.errorCode).toBe('PLUGIN_DATA_DISABLED');
|
||||
});
|
||||
|
||||
await test.step('WebSocket plugin API sends snapshots, relays server events, and rejects p2p relays', async () => {
|
||||
|
||||
Reference in New Issue
Block a user