Files
LocalAI/core/http/react-ui/e2e/slack-addon.spec.js
T
Ettore Di Giacinto 4298715291 feat(connectors): show what a connection will do, and its readiness
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
2026-11-04 16:07:23 +00:00

147 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from './coverage-fixtures.js'
import { mockChat } from './chat-fixtures.js'
import { installStudioAddons } from './studio-addon.js'
import { connectorApi } from './connector-fixtures.js'
// The page of the Slack add-on (the real built bundle, inside the add-on
// shell) with the core API stubbed: both tokens go in as one secret and are
// checked on save, people and channels are two lists of one allowlist, and
// the setup shows the app manifest.
const PAGE = '/app/addons/slack/apps'
const NEWCOMER = { id: 'U042', name: 'Newcomer', count: 2, allowed: false, first: '2026-10-27T08:00:00Z', last: '2026-10-27T08:05:00Z' }
const BINDING = { id: 'b1', name: 'Team app', kind: 'slack', agent_id: 'a1', enabled: true, accept_any: false, allow: ['U001', 'C001'], has_token: true }
const api = (page, initial = []) => connectorApi(page, initial, 'slack', [NEWCOMER])
test.describe('Slack add-on', () => {
test.beforeEach(async ({ page }) => {
await mockChat(page)
await installStudioAddons(page, { ids: ['slack'] })
})
test('creates a binding with both tokens, people and channels, and checks the tokens', async ({ page }) => {
const a = api(page)
await a.install()
await page.route(/\/api\/chats\/connectors\/[^/?]+\/test$/, r => r.fulfill({ json: { bot_id: 'UBOT', bot_username: 'localai (Acme)' } }))
await page.goto(PAGE)
await expect(page.getByText('No Slack apps yet.')).toBeVisible()
await page.getByRole('button', { name: 'New Slack app' }).click()
await page.getByLabel('Name', { exact: true }).fill('Team app')
await expect(page.getByTestId('connector-thread-context')).toHaveCount(0)
await page.getByLabel('Bot token').fill('xoxb-secret-bot')
await expect(page.getByTestId('token-problem')).toContainText('Type both tokens')
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled()
await page.getByLabel('App-level token').fill('xapp-secret-app')
await expect(page.getByTestId('token-problem')).toHaveCount(0)
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
await page.getByRole('textbox', { name: 'Allowed people +' }).fill('U001')
await page.getByRole('textbox', { name: 'Allowed people +' }).press('Enter')
await page.getByRole('textbox', { name: 'Allowed channels +' }).fill('C001')
await page.getByRole('textbox', { name: 'Allowed channels +' }).press('Enter')
await expect(page.getByTestId('connector-allow')).toContainText('U001')
await expect(page.getByTestId('connector-allow')).not.toContainText('C001')
await expect(page.getByTestId('connector-allow-channels')).toContainText('C001')
await page.getByRole('button', { name: 'Save' }).click()
await expect(page.getByTestId('connector-row')).toContainText('Team app')
await expect(page.getByTestId('connector-row')).toContainText('Accepts: 2 listed entries')
await expect(page.getByText('Connected to localai (Acme).')).toBeVisible()
const body = a.s.calls.find(c => c.method === 'POST' && c.url === '/api/chats/connectors').body
expect(body).toMatchObject({ kind: 'slack', name: 'Team app', token: 'xoxb-secret-bot xapp-secret-app', agent_id: 'a1', allow: ['U001', 'C001'] })
expect(body).not.toHaveProperty('bot_token')
// The host always reads the thread of a mention: no setting is offered or sent.
expect(body).not.toHaveProperty('thread_context')
await expect(page.locator('body')).not.toContainText('secret-bot')
})
test('a token the platform refuses is saved and the reason is shown', async ({ page }) => {
await api(page).install()
await page.route(/\/api\/chats\/connectors\/[^/?]+\/test$/, r => r.fulfill({ status: 502, json: { error: { message: 'the platform refused the bot token' } } }))
await page.goto(PAGE)
await page.getByRole('button', { name: 'New Slack app' }).click()
await page.getByLabel('Name', { exact: true }).fill('Team app')
await page.getByLabel('Bot token').fill('xoxb-1')
await page.getByLabel('App-level token').fill('xapp-2')
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
await page.getByRole('button', { name: 'Save' }).click()
await expect(page.getByRole('alert')).toContainText('Saved. The token check failed: the platform refused the bot token')
await expect(page.getByTestId('connector-row')).toHaveCount(1)
})
test('an edit keeps the stored tokens unless both are typed, and keeps both lists', async ({ page }) => {
const a = api(page, [BINDING])
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('App-level token')).toHaveAttribute('placeholder', /Saved/)
await expect(page.getByTestId('connector-allow-channels')).toContainText('C001')
await page.getByRole('button', { name: 'C001 ×' }).click()
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)
expect(put.body.allow).toEqual(['U001'])
})
test('shows how to make the Slack app, with its manifest', async ({ page }) => {
await api(page).install()
await page.goto(PAGE)
const setup = page.getByTestId('slack-setup')
await setup.getByText('How to make the Slack app').click()
await expect(setup).toContainText('connections:write')
await expect(setup.getByLabel('Slack app manifest')).toContainText('socket_mode_enabled: true')
})
test('tests the tokens, shows the sender log and allows a sender', async ({ page }) => {
const a = api(page, [BINDING])
await a.install()
await page.route(/\/api\/chats\/connectors\/[^/?]+\/test$/, r => r.fulfill({ json: { bot_id: 'UBOT', bot_username: 'localai (Acme)' } }))
await page.goto(PAGE)
await page.getByRole('button', { name: 'Test' }).click()
await expect(page.getByText('Connected to localai (Acme).')).toBeVisible()
await page.getByRole('button', { name: 'Senders' }).click()
await expect(page.getByTestId('sender-log')).toContainText('Newcomer · 2 messages · U042')
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/U042/allow')).toBe(true)
})
test('a token the server refuses on save keeps the form open with the reason', async ({ page }) => {
const a = api(page)
await a.install()
await page.route(/\/api\/chats\/connectors(\?.*)?$/, r => {
if (r.request().method() !== 'POST') return r.fallback()
return r.fulfill({ status: 400, json: { error: { message: 'the platform refused the bot token' } } })
})
await page.goto(PAGE)
await page.getByRole('button', { name: 'New Slack app' }).click()
await page.getByLabel('Name', { exact: true }).fill('Team app')
await page.getByLabel('Bot token').fill('xoxb-1')
await page.getByLabel('App-level token').fill('xapp-2')
await page.getByLabel('Agent', { exact: true }).selectOption('a1')
await page.getByTestId('connector-reply').selectOption('progress')
await page.getByRole('button', { name: 'Save' }).click()
await expect(page.getByRole('alert')).toContainText('the platform refused the bot token')
await expect(page.getByRole('form')).toBeVisible()
await expect(page.getByTestId('connector-row')).toHaveCount(0)
expect(a.s.items).toHaveLength(0)
})
test('a list of channels only is named in the readiness list, and a person added fixes it', async ({ page }) => {
const a = api(page, [{ ...BINDING, allow: ['C001'], caps: ['memory.write'] }])
await a.install()
await page.goto(PAGE)
await page.getByRole('button', { name: 'Edit' }).click()
const who = page.getByTestId('connector-ready-who')
await expect(who).toContainText('Only channels are on the list')
await expect(page.getByTestId('connector-can-sender').locator('option')).toContainText(['A person on the allowed list', 'A member of an allowed channel'])
await who.getByRole('button', { name: 'Add a person' }).click()
await page.getByTestId('connector-allow').getByRole('textbox').fill('U001')
await page.getByTestId('connector-allow').getByRole('textbox').press('Enter')
await expect(who).toHaveAttribute('data-state', 'after_save')
await expect.poll(() => [...(a.s.previews.at(-1)?.body.allow || [])].sort()).toEqual(['C001', 'U001'])
})
})