mirror of
https://github.com/mudler/LocalAI.git
synced 2026-11-06 20:43:27 -05:00
The connection page of a messaging add-on listed what a sender could do for the saved settings only, low in the form. Two blocks now sit on the page and read the form as it is. Readiness, at the top of the form, has one row for the token, agent, who may use the bot, permissions, triggers, files and notifications. Each row has a state, one sentence and at most one fix. Only rows that need attention show at first. A ticked permission that nobody can reach, such as a list of channels with no person, is named. "What this bot can do" replaces the old list. For a chosen kind of sender it shows each permission as allowed or blocked, the sentence of the host for the first blocker, and one button taken from the kind of fix: jump to the setting, open the agent in a new tab, open Settings (an administrator only), pair a sender or add a person. It asks the access preview 400 ms after the last change, drops stale answers, works for a connection that is not saved yet, and says when the form has unsaved changes. The latest decisions stay below it. Assisted-by: Claude:claude-sonnet-5-5
257 lines
15 KiB
JavaScript
257 lines
15 KiB
JavaScript
import { test, expect } from './coverage-fixtures.js'
|
|
import { mockChat } from './chat-fixtures.js'
|
|
import { installStudioAddons } from './studio-addon.js'
|
|
import { connectorApi as api, DECLARED } from './connector-fixtures.js'
|
|
|
|
// The page of the Telegram add-on (the real built bundle, inside the add-on
|
|
// shell) with the core API stubbed: the token only goes in, "anyone"
|
|
// needs a confirmation, and a sender in the log can be allowed.
|
|
|
|
const PAGE = '/app/addons/telegram/bots'
|
|
|
|
test.describe('Telegram add-on', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await mockChat(page)
|
|
await installStudioAddons(page, { ids: ['telegram'] })
|
|
})
|
|
|
|
test('creates a binding with a token and an agent, and lists who it accepts', async ({ page }) => {
|
|
const a = api(page)
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
|
|
await expect(page.getByText('No Telegram bots yet.')).toBeVisible()
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
await expect(page.getByRole('option', { name: 'No model (needs a model)' })).toHaveAttribute('disabled', '')
|
|
await page.getByLabel('Name', { exact: true }).fill('Support bot')
|
|
await page.getByLabel('Bot token').fill('555:secret-token')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await page.getByRole('textbox', { name: 'Allowed senders +' }).fill('42')
|
|
await page.getByRole('textbox', { name: 'Allowed senders +' }).press('Enter')
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
|
|
await expect(page.getByTestId('connector-row')).toContainText('Support bot')
|
|
await expect(page.getByTestId('connector-row')).toContainText('Agent: Support')
|
|
const body = a.s.calls.find(c => c.method === 'POST' && c.url === '/api/chats/connectors').body
|
|
expect(body).toMatchObject({ name: 'Support bot', token: '555:secret-token', agent_id: 'a1', allow: ['42'], accept_any: false })
|
|
await expect(page.locator('body')).not.toContainText('secret-token')
|
|
// A new token is checked as soon as it is saved.
|
|
await expect(page.getByText('Connected to @support_bot.')).toBeVisible()
|
|
})
|
|
|
|
test('the token field is empty on edit, and an update without a new token sends none', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: ['42'], has_token: true }])
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'Edit' }).click()
|
|
await expect(page.getByLabel('Bot token')).toHaveValue('')
|
|
await expect(page.getByLabel('Bot token')).toHaveAttribute('placeholder', /Saved/)
|
|
await page.getByLabel('Name', { exact: true }).fill('Renamed')
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
const put = a.s.calls.find(c => c.method === 'PUT')
|
|
expect(put.url).toBe('/api/chats/connectors/b1')
|
|
expect('token' in put.body).toBe(false)
|
|
})
|
|
|
|
test('anyone needs the warning confirmed before it can be saved', async ({ page }) => {
|
|
await api(page).install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
await page.getByLabel('Name', { exact: true }).fill('Public bot')
|
|
await page.getByLabel('Bot token').fill('1:x')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await page.getByRole('radio', { name: 'Anyone who can message the bot' }).check()
|
|
await expect(page.getByTestId('anyone-warning')).toContainText("can use this agent's tools")
|
|
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
|
await page.getByRole('checkbox', { name: 'I understand' }).check()
|
|
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
|
})
|
|
|
|
test('tests the token, shows the sender log and allows a sender', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: [], has_token: true }])
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'Test' }).click()
|
|
await expect(page.getByText('Connected to @support_bot.')).toBeVisible()
|
|
await page.getByRole('button', { name: 'Senders' }).click()
|
|
await expect(page.getByTestId('sender-log')).toContainText('Newcomer · 2 messages · 42')
|
|
await expect(page.getByTestId('sender-log')).toContainText('Ignored')
|
|
await page.getByRole('button', { name: 'Allow', exact: true }).click()
|
|
await expect(page.getByTestId('sender-log')).toContainText('Accepted')
|
|
expect(a.s.calls.some(c => c.url === '/api/chats/connectors/b1/senders/42/allow')).toBe(true)
|
|
})
|
|
|
|
test('switches a binding off and deletes it', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: [], has_token: true }])
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('switch', { name: 'Support bot on' }).uncheck()
|
|
await expect.poll(() => a.s.calls.some(c => c.url.endsWith('/disable'))).toBe(true)
|
|
// Deleting asks first, and says the bot token goes with it.
|
|
await page.getByTestId('connector-row').getByRole('button', { name: 'Delete' }).click()
|
|
const dialog = page.getByRole('alertdialog')
|
|
await expect(dialog).toContainText('Delete Support bot?')
|
|
await expect(dialog).toContainText('bot token')
|
|
await dialog.getByRole('button', { name: 'Cancel' }).click()
|
|
await expect(page.getByTestId('connector-row')).toHaveCount(1)
|
|
expect(a.s.calls.some(c => c.method === 'DELETE')).toBe(false)
|
|
|
|
await page.getByTestId('connector-row').getByRole('button', { name: 'Delete' }).click()
|
|
await dialog.getByRole('button', { name: 'Delete' }).click()
|
|
await expect(page.getByTestId('connector-row')).toHaveCount(0)
|
|
})
|
|
|
|
test('a delete the server refuses shows its reason and keeps the binding', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: [], has_token: true }])
|
|
await a.install()
|
|
await page.route(/\/api\/chats\/connectors\/b1$/, r => r.fulfill({ status: 500, json: { error: { message: 'the store is read-only' } } }))
|
|
await page.goto(PAGE)
|
|
await page.getByTestId('connector-row').getByRole('button', { name: 'Delete' }).click()
|
|
await page.getByRole('alertdialog').getByRole('button', { name: 'Delete' }).click()
|
|
await expect(page.getByRole('alert')).toContainText('the store is read-only')
|
|
await expect(page.getByTestId('connector-row')).toHaveCount(1)
|
|
})
|
|
|
|
test('sets how the bot answers, and the API keeps what was set at each step', async ({ page }) => {
|
|
const a = api(page)
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
// A new binding shows progress, one chat per person, and sends files.
|
|
await expect(page.getByTestId('connector-reply')).toHaveValue('progress')
|
|
await expect(page.getByTestId('connector-conversation')).toHaveValue('sender')
|
|
await expect(page.getByText('Send /new (or !new) in the app to start a new chat.')).toBeVisible()
|
|
await expect(page.getByTestId('connector-files')).toBeChecked()
|
|
await expect(page.getByTestId('connector-window')).toHaveCount(0)
|
|
await page.getByLabel('Name', { exact: true }).fill('Live bot')
|
|
await page.getByLabel('Bot token').fill('555:secret-token')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await page.getByTestId('connector-reply').selectOption('stream')
|
|
await page.getByTestId('connector-conversation').selectOption('window')
|
|
await page.getByTestId('connector-window').fill('45')
|
|
await page.getByTestId('connector-files').uncheck()
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
|
|
await expect(page.getByTestId('connector-answer-summary')).toHaveText('Answers: live answer · new chat after 45 min')
|
|
const post = a.s.calls.find(c => c.method === 'POST' && c.url === '/api/chats/connectors').body
|
|
expect(post).toMatchObject({ reply_mode: 'stream', conversation: 'window', window_minutes: 45, send_files: false })
|
|
expect(a.s.items[0]).toMatchObject({ reply_mode: 'stream', conversation: 'window', window_minutes: 45, send_files: false })
|
|
|
|
// Edit: the form shows what the API holds; the thread mode drops the window.
|
|
await page.getByRole('button', { name: 'Edit' }).click()
|
|
await expect(page.getByTestId('connector-reply')).toHaveValue('stream')
|
|
await expect(page.getByTestId('connector-conversation')).toHaveValue('window')
|
|
await expect(page.getByTestId('connector-window')).toHaveValue('45')
|
|
await expect(page.getByTestId('connector-files')).not.toBeChecked()
|
|
await page.getByTestId('connector-reply').selectOption('off')
|
|
await page.getByTestId('connector-conversation').selectOption('thread')
|
|
await page.getByTestId('connector-files').check()
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
await expect(page.getByTestId('connector-answer-summary')).toHaveText('Answers: typing · a chat per message')
|
|
const put = a.s.calls.filter(c => c.method === 'PUT').at(-1).body
|
|
expect(put).toMatchObject({ reply_mode: 'off', conversation: 'thread', send_files: true })
|
|
expect(put).not.toHaveProperty('window_minutes')
|
|
expect(a.s.items[0]).toMatchObject({ reply_mode: 'off', conversation: 'thread', send_files: true })
|
|
})
|
|
|
|
test('a window out of range cannot be saved', async ({ page }) => {
|
|
await api(page).install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
await page.getByLabel('Name', { exact: true }).fill('Window bot')
|
|
await page.getByLabel('Bot token').fill('1:x')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await page.getByTestId('connector-conversation').selectOption('window')
|
|
await page.getByTestId('connector-window').fill('0')
|
|
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
|
await page.getByTestId('connector-window').fill('30')
|
|
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled()
|
|
})
|
|
|
|
test('the Files section says which limit applies', async ({ page }) => {
|
|
const a = api(page)
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
const files = page.getByTestId('connector-filelimits')
|
|
await expect(files).toContainText('Now: 1 MiB (limit of the add-on).')
|
|
// The owner can only lower the limit: 2 MiB is above the 1 MiB the kind allows.
|
|
await page.getByTestId('connector-file-send').fill('2')
|
|
await expect(files).toContainText('The add-on allows at most 1 MiB here.')
|
|
await page.getByLabel('Name', { exact: true }).fill('Files bot')
|
|
await page.getByLabel('Bot token').fill('555:secret-token')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
|
|
await page.getByTestId('connector-file-send').fill('0.25')
|
|
await expect(files).toContainText('Now: 256 KiB (limit set for this bot).')
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
const post = a.s.calls.find(c => c.method === 'POST' && c.url === '/api/chats/connectors').body
|
|
expect(post).toMatchObject({ file_send_max_bytes: 256 * 1024, file_receive_max_bytes: 0 })
|
|
})
|
|
|
|
test('a kind that declares abilities shows its own limits', async ({ page }) => {
|
|
const a = api(page, [], 'telegram', [], DECLARED)
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
const files = page.getByTestId('connector-filelimits')
|
|
await expect(files).toContainText('Now: 20 MiB (limit of the add-on).')
|
|
await expect(files).toContainText('Now: 256 KiB (limit of the add-on).')
|
|
// Reading is allowed up to 20 MiB, so 5 MiB is accepted and becomes the limit in effect.
|
|
await page.getByTestId('connector-file-receive').fill('5')
|
|
await expect(files).not.toContainText('allows at most')
|
|
await expect(files).toContainText('Now: 5 MiB (limit set for this bot).')
|
|
})
|
|
|
|
test('an edit shows the limit the owner set and a save with it empty clears it', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: ['42'], has_token: true, file_send_max_bytes: 100 * 1024 }])
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'Edit' }).click()
|
|
await expect(page.getByTestId('connector-file-send')).toHaveValue('0.09765625')
|
|
await expect(page.getByTestId('connector-file-receive')).toHaveValue('')
|
|
await page.getByTestId('connector-file-send').fill('')
|
|
await page.getByRole('button', { name: 'Save' }).click()
|
|
const put = a.s.calls.find(c => c.method === 'PUT').body
|
|
expect(put).toMatchObject({ file_send_max_bytes: 0, file_receive_max_bytes: 0 })
|
|
})
|
|
|
|
test('readiness and the preview follow a new bot: token, agent and who, then the form values reach the preview', async ({ page }) => {
|
|
const a = api(page)
|
|
await a.install()
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'New Telegram bot' }).click()
|
|
const ready = page.getByTestId('connector-readiness')
|
|
await expect(ready.getByTestId('connector-ready-token')).toContainText('Paste the token')
|
|
await expect(ready.getByTestId('connector-ready-agent')).toContainText('Choose the agent')
|
|
await expect(ready.getByTestId('connector-ready-who')).toContainText('Nobody is allowed yet')
|
|
await ready.getByTestId('connector-ready-token').getByRole('button', { name: 'Paste the token' }).click()
|
|
await expect(page.getByLabel('Bot token')).toBeFocused()
|
|
await page.getByLabel('Bot token').fill('555:secret-token')
|
|
await expect(ready.getByTestId('connector-ready-token')).toHaveAttribute('data-state', 'after_save')
|
|
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
|
|
await page.getByRole('textbox', { name: 'Allowed senders +' }).fill('42')
|
|
await page.getByRole('textbox', { name: 'Allowed senders +' }).press('Enter')
|
|
await expect(ready.getByTestId('connector-ready-who')).toHaveAttribute('data-state', 'after_save')
|
|
await expect.poll(() => a.s.previews.at(-1)?.body).toMatchObject({ kind: 'telegram', agent_id: 'a1', allow: ['42'] })
|
|
expect(a.s.previews.at(-1).id).toBe('new')
|
|
// One list of senders: no member-of-a-channel choice.
|
|
await expect(page.getByTestId('connector-can-sender').locator('option')).toHaveText(['A person on the allowed list', 'A person you paired'])
|
|
expect(JSON.stringify(a.s.previews)).not.toContain('secret-token')
|
|
})
|
|
|
|
test('a token the platform refuses shows as not working in the readiness list', async ({ page }) => {
|
|
const a = api(page, [{ id: 'b1', name: 'Support bot', kind: 'telegram', agent_id: 'a1', enabled: true, accept_any: false, allow: ['42'], has_token: true }])
|
|
await a.install()
|
|
await page.route(/\/api\/chats\/connectors\/[^/?]+\/test$/, r => r.fulfill({ status: 502, json: { error: { message: 'Unauthorized' } } }))
|
|
await page.goto(PAGE)
|
|
await page.getByRole('button', { name: 'Test' }).click()
|
|
await page.getByRole('button', { name: 'Edit' }).click()
|
|
const token = page.getByTestId('connector-ready-token')
|
|
await expect(token).toHaveAttribute('data-state', 'broken')
|
|
await expect(token).toContainText('The platform refused the token: Unauthorized')
|
|
await token.getByRole('button', { name: 'Paste a new token' }).click()
|
|
await expect(page.getByLabel('Bot token')).toBeFocused()
|
|
})
|
|
})
|