The channel sends through the Bot API with httpx only: HTML with escaped text, cut to fit a message, silent for a warning, a short 429 waited out once, and errors that say what to do about a blocked bot, a lost chat or a bad token and never quote the request URL. The bot token is a stored secret: saving a channel refuses a literal one, the card's "Store token" writes the secret and a reference, and no endpoint returns it. A chat id is never typed. Connect starts a ten minute code, and the chat that sends `/start <code>` back through the bot is the one connected, so it is a chat somebody opened a link for. The engine fetches updates only while a code is waiting (no standing poll, no webhook, no inbound address) and ignores everything else without a reply. Connect needs a saved form, and the form takes the connected chat in without touching what was typed since. The token is part of every Bot API URL, so the httpx logger gets the same filter the request lines have, which now also masks an httpx.URL argument.
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
import { expect, test } from "@playwright/test"
|
|
import { firstSuperuser, firstSuperuserPassword } from "./config.ts"
|
|
import { api, apiPage } from "./utils/api.ts"
|
|
import { createUser } from "./utils/privateApi.ts"
|
|
import { randomEmail, randomPassword } from "./utils/random.ts"
|
|
import { logInUser } from "./utils/user.ts"
|
|
|
|
/**
|
|
* The Alerts page: channels and rules are a superuser's, and the Telegram
|
|
* card asks for a stored token and a saved channel before it connects a chat.
|
|
* The bot itself is never reached, so the engine's answers are stubbed.
|
|
*/
|
|
|
|
test.use({ storageState: { cookies: [], origins: [] } })
|
|
test.describe.configure({ mode: "serial" })
|
|
|
|
const channel = "tg_spec"
|
|
let original: { channels?: unknown[] } = {}
|
|
|
|
test.beforeAll(async ({ browser }) => {
|
|
const page = await apiPage(browser)
|
|
original = await (await api(page, "/alerts/config")).json()
|
|
await api(page, "/alerts/config", {
|
|
method: "PUT",
|
|
data: {
|
|
...original,
|
|
channels: [
|
|
...(original.channels ?? []),
|
|
{
|
|
name: channel,
|
|
kind: "telegram",
|
|
enabled: true,
|
|
config: { token: { $secret: "telegram-bot" } },
|
|
},
|
|
],
|
|
},
|
|
})
|
|
await page.close()
|
|
})
|
|
|
|
test.afterAll(async ({ browser }) => {
|
|
const page = await apiPage(browser)
|
|
await api(page, "/alerts/config", { method: "PUT", data: original })
|
|
await page.close()
|
|
})
|
|
|
|
test("a new Telegram channel cannot connect before it has a token", async ({
|
|
page,
|
|
}) => {
|
|
await logInUser(page, firstSuperuser, firstSuperuserPassword)
|
|
await page.goto("/alerts")
|
|
|
|
await page.getByTestId("add-channel").click()
|
|
await page.getByLabel("Channel kind").last().click()
|
|
await page.getByRole("option", { name: "telegram" }).click()
|
|
|
|
const card = page.getByTestId("telegram-connect").last()
|
|
await expect(card.getByText("Not connected")).toBeVisible()
|
|
await expect(card.getByTestId("telegram-connect-chat")).toBeDisabled()
|
|
// The token is typed into a password field and kept in the secrets store.
|
|
await expect(card.getByLabel("Bot token", { exact: true })).toHaveAttribute(
|
|
"type",
|
|
"password",
|
|
)
|
|
})
|
|
|
|
test("a connected chat is taken into the form without leaving it unsaved", async ({
|
|
page,
|
|
}) => {
|
|
let linked = false
|
|
await page.route("**/api/v1/alerts/telegram/*/link", async (route) => {
|
|
if (route.request().method() === "POST") {
|
|
return route.fulfill({
|
|
json: {
|
|
url: "https://t.me/fluksio_bot?start=code",
|
|
group_url: "https://t.me/fluksio_bot?startgroup=code",
|
|
expires_in: 600,
|
|
},
|
|
})
|
|
}
|
|
// The chat answers on the first look.
|
|
linked = true
|
|
return route.fulfill({ json: { waiting: false, expires_in: 0, error: "" } })
|
|
})
|
|
await page.route("**/api/v1/alerts/config", async (route) => {
|
|
const response = await route.fetch()
|
|
if (route.request().method() !== "GET" || !linked) {
|
|
return route.fulfill({ response })
|
|
}
|
|
const stored = await response.json()
|
|
for (const c of stored.channels) {
|
|
if (c.name === channel) {
|
|
c.config = { ...c.config, chat_id: "42", label: "Anna" }
|
|
}
|
|
}
|
|
return route.fulfill({ response, json: stored })
|
|
})
|
|
await logInUser(page, firstSuperuser, firstSuperuserPassword)
|
|
await page.goto("/alerts")
|
|
|
|
const card = page.getByTestId("telegram-connect").last()
|
|
await card.getByTestId("telegram-connect-chat").click()
|
|
|
|
await expect(card.getByText("Connected to Anna (private chat)")).toBeVisible()
|
|
await expect(page.getByTestId("save-alerts")).toBeDisabled()
|
|
})
|
|
|
|
test("an ordinary account sees only the button for its own browser", async ({
|
|
page,
|
|
}) => {
|
|
const email = randomEmail()
|
|
const password = randomPassword()
|
|
await createUser({ email, password })
|
|
await logInUser(page, email, password)
|
|
|
|
await page.goto("/alerts")
|
|
|
|
await expect(page.getByTestId("this-browser")).toBeVisible()
|
|
await expect(page.getByTestId("add-channel")).toHaveCount(0)
|
|
})
|