mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-29 22:23:01 +00:00
* feat(webhooks): refactor webhook URL handling and enhance API endpoints - Introduced a new utility function `buildWebhookIngressUrl` to standardize the construction of webhook URLs across components. - Updated `WebhooksDebugPanel` and `TunnelList` components to utilize the new URL builder for improved consistency and maintainability. - Refactored API endpoints in `tunnelsApi` to reflect changes in webhook routing, ensuring all references to tunnel management are aligned with the new structure. - Adjusted user API calls to replace `/telegram/me` with `/auth/me` for better clarity and consistency in authentication flows. - Enhanced socket service to normalize channel connection update payloads, improving error handling and data integrity. These changes streamline webhook management and enhance the overall developer experience when working with webhooks. * feat(messaging): enhance Telegram and Discord channel linking functionality - Added support for managed DM linking with Telegram and OAuth flow for Discord. - Introduced `createChannelLinkToken` API to generate short-lived link tokens for messaging channels. - Updated `MessagingPanel` to handle managed linking flows, including building launch URLs and user instructions. - Enhanced configuration to include a Telegram bot username for fallback linking. - Updated tests to mock new configuration values for consistent testing. These changes improve the user experience for linking messaging channels and streamline the authentication process. * feat(api): introduce core command client and refactor API calls - Added a new `coreCommandClient` to facilitate RPC calls to core services. - Refactored existing API endpoints in `authApi`, `billingApi`, `creditsApi`, `tunnelsApi`, and `userApi` to utilize the new command client for improved consistency and maintainability. - Updated the billing API to include new endpoints for fetching balance, transactions, and managing auto-recharge settings. - Enhanced the user API to streamline user data retrieval. - Improved error handling and logging across the refactored API calls. These changes enhance the overall architecture of the API services, making them more modular and easier to maintain. * refactor(api): streamline API calls and enhance error handling - Refactored `creditsApi` and `tunnelsApi` to ensure consistent use of the new core command client. - Updated API methods to improve error handling and response parsing. - Introduced a new `authed_json` method in `BackendOAuthClient` for standardized authenticated requests. - Cleaned up unused imports and optimized code structure across various modules. These changes enhance the maintainability and reliability of the API services. * refactor(api): streamline API calls and improve code readability - Reorganized API calls in `authApi`, `billingApi`, `creditsApi`, and `tunnelsApi` to enhance consistency and maintainability. - Updated function formatting for better readability, ensuring parameters are clearly defined. - Cleaned up import statements and removed unnecessary line breaks to improve code clarity. These changes contribute to a more maintainable codebase and enhance the overall developer experience. * refactor(MessagingPanel): clean up imports and remove unused constants - Reorganized import statements for clarity and consistency. - Removed unused constants related to channel definitions and status styles to streamline the component. - Simplified the `updateField` function call in the rendering logic for better readability. These changes enhance the maintainability of the MessagingPanel component. * fix: restore helper functions removed during merge cleanup buildManagedChannelLaunchUrl and buildManagedChannelInstruction were stripped by the linter after merge resolution, breaking the build. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(messaging): enhance MessagingPanel with pending instruction handling - Introduced a new state for pending instructions to improve user feedback during channel connection processes. - Updated error handling to ensure users receive clear instructions, including URLs for manual access if automatic opening fails. - Refactored connection status updates to streamline the dispatch process and improve code readability. - Enhanced the rendering logic to display pending instructions in the UI, providing better context for users during authentication flows. * fix(api): encode channel and ID parameters in webhook and OAuth URLs - Updated the URL construction in the BackendOAuthClient to encode the channel parameter, ensuring proper formatting for requests. - Modified the get_tunnel, update_tunnel, and delete_tunnel functions to encode the ID parameter in webhook URLs, enhancing security and compatibility with special characters. * fix(auth): validate channel input for link token creation - Added validation to ensure the channel is not empty and is one of the supported types (telegram, discord). - Converted the channel string to lowercase for consistent matching. - Updated the call to create_channel_link_token to use a reference to the channel variable. * chore(todos): update TODO list with completed tasks and new feature requests - Marked several tasks as completed, including integration of the custom memory engine and skills registry into the core. - Added new items to the TODO list, including improvements for voiceover functionalities, screen intelligence, and Gmail skill enhancements. - Included tasks for debugging skills from the UI and improving prompts to reduce hallucinations. * refactor(webhooks): improve formatting of get_tunnel function for readability - Reformatted the get_tunnel function to enhance code clarity by adjusting the indentation and line breaks in the call to get_authed_value. - This change improves the maintainability of the code and aligns with the overall code style. * test(userApi): enhance userApi tests with improved mocking and error handling - Added a mock function for core command calls to streamline test setup. - Introduced a helper function to generate mock user data for consistent test cases. - Updated tests to use the mock function for simulating API responses, improving clarity and maintainability. - Enhanced error handling in tests to cover various API error scenarios, ensuring robustness in userApi.getMe functionality. * refactor(billingApi): update tests to use core command mocking and improve error handling - Replaced apiClient mocks with core command mocks for better alignment with the new API structure. - Updated test cases to reflect changes in API call expectations, enhancing clarity and maintainability. - Improved error handling in tests to ensure proper propagation of errors from core command calls. * refactor(billingApi): simplify test cases by consolidating mock function calls - Streamlined mock function calls in billingApi tests for improved readability and consistency. - Removed unnecessary line breaks in mock responses, enhancing clarity in test definitions. - Ensured that error handling and API call expectations are clearly represented in the tests. * refactor(api): rename settings endpoint to current_user for clarity - Updated the endpoint handling authenticated profile fetches to improve clarity in the codebase. - Adjusted the routing to reflect the new function name, enhancing maintainability and understanding of the API's purpose. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1017 lines
37 KiB
TypeScript
1017 lines
37 KiB
TypeScript
/* eslint-disable */
|
|
// @ts-nocheck
|
|
/**
|
|
* E2E test: Telegram Integration Flows.
|
|
*
|
|
* Covers:
|
|
* 7.1.1 /start Command Handling — "Message OpenHuman" button entry point
|
|
* 7.1.2 Telegram ID Mapping — Telegram skill appears in SkillsGrid with status
|
|
* 7.1.3 Duplicate TG Account Prevention — setup returns duplicate error
|
|
* 7.2.1 Read Access — Telegram skill listed in Intelligence page
|
|
* 7.2.2 Write Access — Telegram skill present with write-capable tools
|
|
* 7.2.3 Initiate Action Enforcement — "Message OpenHuman" accessible for auth users
|
|
* 7.3.1 Valid Command — "Message OpenHuman" button is clickable
|
|
* 7.3.2 Invalid Command — skill status reflects error state
|
|
* 7.3.3 Unauthorized Action — unauthorized status shown when mock returns 403
|
|
* 7.4.1 Telegram Webhook — app makes expected webhook configuration call
|
|
* 7.5.1 Bot Unlink — Disconnect flow with confirmation dialog
|
|
* 7.5.3 Re-Run Setup — setup wizard accessible after disconnect
|
|
* 7.5.4 Permission Re-Sync — skill status refreshes after reconnect
|
|
*
|
|
* The mock server runs on http://127.0.0.1:18473 and the .app bundle must
|
|
* have been built with VITE_BACKEND_URL pointing there.
|
|
*/
|
|
import { waitForApp } from '../helpers/app-helpers';
|
|
import { triggerAuthDeepLink } from '../helpers/deep-link-helpers';
|
|
import {
|
|
clickButton,
|
|
clickNativeButton,
|
|
clickText,
|
|
dumpAccessibilityTree,
|
|
textExists,
|
|
waitForText,
|
|
} from '../helpers/element-helpers';
|
|
import {
|
|
navigateToHome,
|
|
navigateToIntelligence,
|
|
navigateToSettings,
|
|
navigateToSkills,
|
|
navigateViaHash,
|
|
performFullLogin,
|
|
waitForHomePage,
|
|
} from '../helpers/shared-flows';
|
|
import {
|
|
clearRequestLog,
|
|
getRequestLog,
|
|
resetMockBehavior,
|
|
setMockBehavior,
|
|
startMockServer,
|
|
stopMockServer,
|
|
} from '../mock-server';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Shared helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const LOG_PREFIX = '[TelegramFlow]';
|
|
|
|
async function waitForRequest(method, urlFragment, timeout = 15_000) {
|
|
const deadline = Date.now() + timeout;
|
|
while (Date.now() < deadline) {
|
|
const log = getRequestLog();
|
|
const match = log.find(r => r.method === method && r.url.includes(urlFragment));
|
|
if (match) return match;
|
|
await browser.pause(500);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
async function waitForTextToDisappear(text, timeout = 10_000) {
|
|
const deadline = Date.now() + timeout;
|
|
while (Date.now() < deadline) {
|
|
if (!(await textExists(text))) return true;
|
|
await browser.pause(500);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Counter for unique JWT suffixes.
|
|
*/
|
|
let reAuthCounter = 0;
|
|
|
|
/**
|
|
* Re-authenticate via deep link and navigate to Home.
|
|
*/
|
|
async function reAuthAndGoHome(token = 'e2e-telegram-token') {
|
|
clearRequestLog();
|
|
|
|
reAuthCounter += 1;
|
|
setMockBehavior('jwt', `telegram-reauth-${reAuthCounter}`);
|
|
|
|
await triggerAuthDeepLink(token);
|
|
await browser.pause(5_000);
|
|
|
|
await navigateToHome();
|
|
|
|
const homeText = await waitForHomePage(15_000);
|
|
if (!homeText) {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} reAuth: Home page not reached. Tree:\n`, tree.slice(0, 4000));
|
|
throw new Error('reAuthAndGoHome: Home page not reached');
|
|
}
|
|
console.log(`${LOG_PREFIX} Re-authed (jwt suffix telegram-reauth-${reAuthCounter}), on Home`);
|
|
}
|
|
|
|
/**
|
|
* Attempt to find the Telegram skill in the UI.
|
|
* Checks Home page first, then falls back to Intelligence page.
|
|
* Returns true if Telegram was found, false otherwise.
|
|
*/
|
|
async function findTelegramInUI() {
|
|
// Check Home page (SkillsGrid)
|
|
if (await textExists('Telegram')) {
|
|
console.log(`${LOG_PREFIX} Telegram found on Home page`);
|
|
return true;
|
|
}
|
|
|
|
// Check Intelligence page
|
|
try {
|
|
await navigateToIntelligence();
|
|
if (await textExists('Telegram')) {
|
|
console.log(`${LOG_PREFIX} Telegram found on Intelligence page`);
|
|
return true;
|
|
}
|
|
} catch {
|
|
console.log(`${LOG_PREFIX} Could not navigate to Intelligence page`);
|
|
}
|
|
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} Telegram not found in UI. Tree:\n`, tree.slice(0, 4000));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Navigate to the Settings Connections panel.
|
|
* Settings → /settings/connections via ConnectionsPanel.
|
|
*/
|
|
async function navigateToConnections(maxAttempts = 3) {
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
await navigateToSettings();
|
|
console.log(`${LOG_PREFIX} Settings nav (attempt ${attempt})`);
|
|
await browser.pause(3_000);
|
|
|
|
// Look for Connections menu item or direct Telegram entry
|
|
const connectionsCandidates = ['Connections', 'Connected Accounts', 'Integrations'];
|
|
let clicked = false;
|
|
for (const text of connectionsCandidates) {
|
|
if (await textExists(text)) {
|
|
await clickText(text, 10_000);
|
|
console.log(`${LOG_PREFIX} Clicked "${text}" in Settings`);
|
|
clicked = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (clicked) {
|
|
await browser.pause(2_000);
|
|
return true;
|
|
}
|
|
|
|
// If no Connections menu item, check if Telegram is directly visible in Settings
|
|
if (await textExists('Telegram')) {
|
|
console.log(`${LOG_PREFIX} Telegram directly visible in Settings`);
|
|
return true;
|
|
}
|
|
|
|
console.log(`${LOG_PREFIX} Connections not found on attempt ${attempt}, retrying...`);
|
|
await browser.pause(2_000);
|
|
}
|
|
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(
|
|
`${LOG_PREFIX} Connections not found after ${maxAttempts} attempts. Tree:\n`,
|
|
tree.slice(0, 6000)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Open the Telegram skill setup/management modal.
|
|
* Expects Telegram to be visible and clickable on the current page.
|
|
*/
|
|
async function openTelegramModal() {
|
|
if (!(await textExists('Telegram'))) {
|
|
console.log(`${LOG_PREFIX} Telegram not visible on current page`);
|
|
return false;
|
|
}
|
|
|
|
await clickText('Telegram', 10_000);
|
|
await browser.pause(2_000);
|
|
|
|
// Check for either "Connect Telegram" (setup) or "Manage Telegram" (management panel)
|
|
const hasConnect = await textExists('Connect Telegram');
|
|
const hasManage = await textExists('Manage Telegram');
|
|
|
|
if (hasConnect) {
|
|
console.log(`${LOG_PREFIX} Telegram setup modal opened ("Connect Telegram")`);
|
|
return 'connect';
|
|
}
|
|
if (hasManage) {
|
|
console.log(`${LOG_PREFIX} Telegram management panel opened ("Manage Telegram")`);
|
|
return 'manage';
|
|
}
|
|
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} Telegram modal not recognized. Tree:\n`, tree.slice(0, 4000));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Close any open modal by clicking outside or pressing Escape.
|
|
*/
|
|
async function closeModalIfOpen() {
|
|
// Try to find and click a close/cancel button
|
|
const closeCandidates = ['Close', 'Cancel', 'Done'];
|
|
for (const text of closeCandidates) {
|
|
if (await textExists(text)) {
|
|
try {
|
|
await clickText(text, 3_000);
|
|
await browser.pause(1_000);
|
|
return;
|
|
} catch {
|
|
// Try next
|
|
}
|
|
}
|
|
}
|
|
// Try pressing Escape via native button
|
|
try {
|
|
await browser.keys(['Escape']);
|
|
await browser.pause(1_000);
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Test suite
|
|
// ===========================================================================
|
|
|
|
// TEMPORARILY DISABLED: This test suite was designed for the skill system integration
|
|
// which has been replaced by the unified Telegram system. New tests for the unified
|
|
// system need to be written.
|
|
describe.skip('Telegram Integration Flows', () => {
|
|
before(async () => {
|
|
await startMockServer();
|
|
await waitForApp();
|
|
clearRequestLog();
|
|
|
|
// Full login + onboarding — lands on Home
|
|
await performFullLogin('e2e-telegram-flow-token');
|
|
|
|
// Ensure we're on Home
|
|
await navigateToHome();
|
|
});
|
|
|
|
after(async function () {
|
|
this.timeout(30_000);
|
|
resetMockBehavior();
|
|
try {
|
|
await stopMockServer();
|
|
} catch (err) {
|
|
console.log(`${LOG_PREFIX} stopMockServer error (non-fatal):`, err);
|
|
}
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7.1 Account Linking
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('7.1 Account Linking', () => {
|
|
it('7.1.1 — /start Command Handling: "Message OpenHuman" button exists on Home', async () => {
|
|
// Ensure we're on Home
|
|
await navigateToHome();
|
|
|
|
// Verify "Message OpenHuman" button is present — this is the /start entry point
|
|
const hasButton = await textExists('Message OpenHuman');
|
|
if (!hasButton) {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} 7.1.1: Home page tree:\n`, tree.slice(0, 6000));
|
|
}
|
|
expect(hasButton).toBe(true);
|
|
console.log(`${LOG_PREFIX} 7.1.1: "Message OpenHuman" button found on Home page`);
|
|
|
|
// Verify Telegram skill or related content is somewhere in the app
|
|
// (Telegram drives the "Message OpenHuman" integration)
|
|
const hasTelegram = await findTelegramInUI();
|
|
if (!hasTelegram) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.1: Telegram skill not visible in UI — V8 runtime may not ` +
|
|
`have discovered it. The "Message OpenHuman" button still confirms /start entry point.`
|
|
);
|
|
}
|
|
|
|
// Navigate back to Home before next test
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.1.1 PASSED`);
|
|
});
|
|
|
|
it('7.1.2 — Telegram ID Mapping: Telegram skill shows status indicator', async () => {
|
|
// Ensure we're on Home
|
|
await navigateToHome();
|
|
|
|
const telegramVisible = await findTelegramInUI();
|
|
|
|
if (!telegramVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.2: Telegram skill not discovered by V8 runtime. ` +
|
|
`Skipping status check — skill discovery is environment-dependent.`
|
|
);
|
|
// Navigate back to Home and pass gracefully
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Telegram is visible — verify it shows a status indicator
|
|
// Valid status texts: "Setup Required", "Offline", "Connected", "Connecting",
|
|
// "Not Authenticated", "Disconnected", "Error"
|
|
const statusTexts = [
|
|
'Setup Required',
|
|
'Offline',
|
|
'Connected',
|
|
'Connecting',
|
|
'Not Authenticated',
|
|
'Disconnected',
|
|
'Error',
|
|
'setup_required',
|
|
'offline',
|
|
'connected',
|
|
'disconnected',
|
|
'error',
|
|
];
|
|
|
|
let foundStatus = null;
|
|
for (const status of statusTexts) {
|
|
if (await textExists(status)) {
|
|
foundStatus = status;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundStatus) {
|
|
console.log(`${LOG_PREFIX} 7.1.2: Telegram status indicator found: "${foundStatus}"`);
|
|
} else {
|
|
// Status indicator may use icon-only UI — just verify Telegram text is present
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.2: No text status found, but Telegram is present in UI ` +
|
|
`(may use icon-only status indicator)`
|
|
);
|
|
}
|
|
|
|
// The key assertion: Telegram skill is present in the UI
|
|
expect(telegramVisible).toBe(true);
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.1.2 PASSED`);
|
|
});
|
|
|
|
it('7.1.3 — Duplicate TG Account Prevention: setup returns duplicate error', async () => {
|
|
// Set mock to return duplicate error for Telegram connect
|
|
setMockBehavior('telegramDuplicate', 'true');
|
|
|
|
await navigateToHome();
|
|
|
|
// Try to open Telegram skill from the connections panel
|
|
const connectionsFound = await navigateToConnections();
|
|
if (!connectionsFound) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: Connections panel not found. ` +
|
|
`Testing via Home page SkillsGrid instead.`
|
|
);
|
|
await navigateToHome();
|
|
}
|
|
|
|
await browser.pause(1_000);
|
|
|
|
// Attempt to open Telegram modal
|
|
const modalState = await openTelegramModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: Could not open Telegram modal — skill may not be discovered. ` +
|
|
`Verifying mock endpoint is reachable instead.`
|
|
);
|
|
|
|
// Verify the duplicate endpoint returns the error via mock request log check
|
|
clearRequestLog();
|
|
// The endpoint would be called during OAuth flow — verify it's configured correctly
|
|
const connectCall = await waitForRequest('GET', '/auth/telegram/connect', 3_000);
|
|
if (!connectCall) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: No connect request made (modal not opened). ` +
|
|
`Mock duplicate behavior is configured. Test passes as environment-dependent.`
|
|
);
|
|
}
|
|
setMockBehavior('telegramDuplicate', 'false');
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Setup wizard is open — verify "Connect Telegram" title
|
|
const hasConnectTitle = await textExists('Connect Telegram');
|
|
expect(hasConnectTitle).toBe(true);
|
|
console.log(`${LOG_PREFIX} 7.1.3: "Connect Telegram" setup modal is open`);
|
|
|
|
// The duplicate error would occur during the OAuth flow when the backend
|
|
// is called. Since we can't complete the full OAuth flow in E2E tests,
|
|
// we verify the mock endpoint is set up to return the duplicate error.
|
|
clearRequestLog();
|
|
|
|
// Check if there's a connect/start button to click
|
|
const connectButtonCandidates = ['Connect', 'Start', 'Authorize', 'Begin Setup'];
|
|
for (const btn of connectButtonCandidates) {
|
|
if (await textExists(btn)) {
|
|
await clickText(btn, 5_000);
|
|
await browser.pause(3_000);
|
|
|
|
// After clicking, check if a request was made that would trigger duplicate error
|
|
const connectRequest = await waitForRequest('GET', '/auth/telegram/connect', 5_000);
|
|
if (connectRequest) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: Connect request made — duplicate error mock is active`
|
|
);
|
|
}
|
|
|
|
// Look for error message in the UI
|
|
const errorCandidates = [
|
|
'already linked',
|
|
'duplicate',
|
|
'already connected',
|
|
'already exists',
|
|
'error',
|
|
'Error',
|
|
];
|
|
let foundError = false;
|
|
for (const errText of errorCandidates) {
|
|
if (await textExists(errText)) {
|
|
console.log(`${LOG_PREFIX} 7.1.3: Error message found: "${errText}"`);
|
|
foundError = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundError) {
|
|
console.log(`${LOG_PREFIX} 7.1.3: Duplicate account error displayed to user`);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: Error message not visible (OAuth redirects to external browser)`
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if (modalState === 'manage') {
|
|
// Already connected — duplicate prevention is implicitly tested
|
|
console.log(
|
|
`${LOG_PREFIX} 7.1.3: Telegram already connected (management panel). ` +
|
|
`Duplicate prevention applies at re-connect attempt.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
setMockBehavior('telegramDuplicate', 'false');
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.1.3 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7.2 Permission Levels
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('7.2 Permission Levels', () => {
|
|
it('7.2.1 — Read Access: Telegram skill listed in Intelligence page', async () => {
|
|
// Reset to default state and re-auth
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-telegram-read-token');
|
|
|
|
// Navigate to Intelligence page to see skills list
|
|
try {
|
|
await navigateToIntelligence();
|
|
console.log(`${LOG_PREFIX} 7.2.1: Navigated to Intelligence page`);
|
|
} catch {
|
|
console.log(`${LOG_PREFIX} 7.2.1: Intelligence nav not found — checking Home for skills`);
|
|
await navigateToHome();
|
|
}
|
|
|
|
// Check if Telegram is listed (indicates the skill system is running)
|
|
const telegramInIntelligence = await textExists('Telegram');
|
|
|
|
if (telegramInIntelligence) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.1: Telegram found on Intelligence page — read access available`
|
|
);
|
|
expect(telegramInIntelligence).toBe(true);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.1: Telegram not visible on Intelligence page. ` +
|
|
`Checking Home page as fallback.`
|
|
);
|
|
await navigateToHome();
|
|
const telegramOnHome = await textExists('Telegram');
|
|
if (telegramOnHome) {
|
|
console.log(`${LOG_PREFIX} 7.2.1: Telegram found on Home page — read access available`);
|
|
expect(telegramOnHome).toBe(true);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.1: Telegram skill not discovered in current environment. ` +
|
|
`Passing — skill discovery is V8 runtime-dependent.`
|
|
);
|
|
}
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.2.1 PASSED`);
|
|
});
|
|
|
|
it('7.2.2 — Write Access: Telegram skill present with write-capable status', async () => {
|
|
resetMockBehavior();
|
|
setMockBehavior('telegramPermission', 'write');
|
|
await reAuthAndGoHome('e2e-telegram-write-token');
|
|
|
|
// The Telegram skill has 99 MCP tools including send-message, edit-message, etc.
|
|
// Write access is indicated by the skill being "connected" with full tool access.
|
|
const telegramVisible = await findTelegramInUI();
|
|
|
|
if (!telegramVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.2: Telegram skill not in UI — ` +
|
|
`V8 runtime environment-dependent. Checking mock permissions endpoint.`
|
|
);
|
|
|
|
// Mock is configured to return write permissions — verified by setMockBehavior call above
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.2: Mock configured with permission level: write (set via setMockBehavior)`
|
|
);
|
|
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Telegram is visible — verify the "Message OpenHuman" button exists
|
|
// (the bot interaction button requires write access to Telegram)
|
|
await navigateToHome();
|
|
const hasMessageButton = await textExists('Message OpenHuman');
|
|
expect(hasMessageButton).toBe(true);
|
|
console.log(
|
|
`${LOG_PREFIX} 7.2.2: "Message OpenHuman" button present — write-capable tools accessible`
|
|
);
|
|
|
|
console.log(`${LOG_PREFIX} 7.2.2 PASSED`);
|
|
});
|
|
|
|
it('7.2.3 — Initiate Action Enforcement: "Message OpenHuman" accessible for auth users', async () => {
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-telegram-initiate-token');
|
|
|
|
// Ensure we're on Home
|
|
await navigateToHome();
|
|
|
|
// Verify the "Message OpenHuman" button exists and is clickable
|
|
const hasButton = await textExists('Message OpenHuman');
|
|
expect(hasButton).toBe(true);
|
|
console.log(`${LOG_PREFIX} 7.2.3: "Message OpenHuman" button is present for auth user`);
|
|
|
|
// The button should be interactable — it's the entry point for initiating Telegram actions
|
|
const buttonEl = await waitForText('Message OpenHuman', 10_000);
|
|
const isExisting = await buttonEl.isExisting();
|
|
expect(isExisting).toBe(true);
|
|
|
|
console.log(`${LOG_PREFIX} 7.2.3: "Message OpenHuman" is accessible for authenticated user`);
|
|
console.log(`${LOG_PREFIX} 7.2.3 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7.3 Command Processing
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('7.3 Command Processing', () => {
|
|
it('7.3.1 — Valid Command: "Message OpenHuman" button is clickable', async () => {
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-telegram-cmd-valid-token');
|
|
await navigateToHome();
|
|
|
|
// Verify the button exists
|
|
const hasButton = await textExists('Message OpenHuman');
|
|
expect(hasButton).toBe(true);
|
|
|
|
clearRequestLog();
|
|
|
|
// Click "Message OpenHuman" — this triggers the Telegram bot interaction
|
|
// In production, this opens the Telegram bot URL
|
|
// In testing, we verify the button is clickable without errors
|
|
const el = await waitForText('Message OpenHuman', 10_000);
|
|
const loc = await el.getLocation();
|
|
const sz = await el.getSize();
|
|
const centerX = Math.round(loc.x + sz.width / 2);
|
|
const centerY = Math.round(loc.y + sz.height / 2);
|
|
|
|
await browser.performActions([
|
|
{
|
|
type: 'pointer',
|
|
id: 'mouse1',
|
|
parameters: { pointerType: 'mouse' },
|
|
actions: [
|
|
{ type: 'pointerMove', duration: 10, x: centerX, y: centerY },
|
|
{ type: 'pointerDown', button: 0 },
|
|
{ type: 'pause', duration: 50 },
|
|
{ type: 'pointerUp', button: 0 },
|
|
],
|
|
},
|
|
]);
|
|
await browser.releaseActions();
|
|
console.log(`${LOG_PREFIX} 7.3.1: Clicked "Message OpenHuman" button`);
|
|
await browser.pause(2_000);
|
|
|
|
// After clicking, the button should remain on the page (it opens an external URL)
|
|
// or navigate away — either is valid behavior
|
|
const stillHasButton = await textExists('Message OpenHuman');
|
|
const isOnHome = await waitForHomePage(5_000);
|
|
// The button click either opens external URL (button still there) or navigates
|
|
// Both outcomes are valid — just ensure no crash occurred
|
|
console.log(
|
|
`${LOG_PREFIX} 7.3.1: After click — button still visible: ${stillHasButton}, ` +
|
|
`home detected: ${!!isOnHome}`
|
|
);
|
|
|
|
// Navigate back to Home for cleanup
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.3.1 PASSED`);
|
|
});
|
|
|
|
it('7.3.2 — Invalid Command: skill status reflects error state when configured', async () => {
|
|
resetMockBehavior();
|
|
setMockBehavior('telegramCommandError', 'true');
|
|
setMockBehavior('telegramSkillStatus', 'error');
|
|
|
|
await reAuthAndGoHome('e2e-telegram-cmd-invalid-token');
|
|
await navigateToHome();
|
|
|
|
// Verify we can still navigate the UI despite error mock
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.3.2: Home page accessible despite error mock: "${homeMarker}"`);
|
|
|
|
// Check if Telegram shows an error status (environment-dependent)
|
|
const telegramVisible = await findTelegramInUI();
|
|
if (telegramVisible) {
|
|
const hasErrorStatus =
|
|
(await textExists('Error')) ||
|
|
(await textExists('error')) ||
|
|
(await textExists('Disconnected')) ||
|
|
(await textExists('Failed'));
|
|
console.log(`${LOG_PREFIX} 7.3.2: Telegram visible, error status shown: ${hasErrorStatus}`);
|
|
// Note: The actual error text depends on the skill status mapping — log but don't fail
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.3.2: Telegram skill not in UI — ` +
|
|
`error state test is environment-dependent.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.3.2 PASSED`);
|
|
});
|
|
|
|
it('7.3.3 — Unauthorized Action: unauthorized status when mock returns 403', async () => {
|
|
resetMockBehavior();
|
|
setMockBehavior('telegramUnauthorized', 'true');
|
|
setMockBehavior('telegramSkillStatus', 'error');
|
|
|
|
await reAuthAndGoHome('e2e-telegram-unauth-token');
|
|
await navigateToHome();
|
|
|
|
// Verify the app remains usable despite unauthorized mock
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.3.3: Home page accessible with unauthorized mock`);
|
|
|
|
// Verify "Message OpenHuman" button may still be present
|
|
// (UI should degrade gracefully — not crash)
|
|
const hasButton = await textExists('Message OpenHuman');
|
|
console.log(
|
|
`${LOG_PREFIX} 7.3.3: "Message OpenHuman" button present despite unauthorized mock: ${hasButton}`
|
|
);
|
|
|
|
// Check Telegram status in skills grid
|
|
const telegramVisible = await findTelegramInUI();
|
|
if (telegramVisible) {
|
|
console.log(`${LOG_PREFIX} 7.3.3: Telegram visible in UI with unauthorized mock active`);
|
|
// The skill may show an error/disconnected state
|
|
const hasAuthError =
|
|
(await textExists('Unauthorized')) ||
|
|
(await textExists('Error')) ||
|
|
(await textExists('Not Authenticated')) ||
|
|
(await textExists('Disconnected'));
|
|
console.log(`${LOG_PREFIX} 7.3.3: Auth error status visible: ${hasAuthError}`);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.3.3 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7.4 Webhook Handling
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('7.4 Webhook Handling', () => {
|
|
it('7.4.1 — Telegram Webhook: app makes webhook configuration call when skill active', async () => {
|
|
resetMockBehavior();
|
|
setMockBehavior('telegramSetupComplete', 'true');
|
|
|
|
await reAuthAndGoHome('e2e-telegram-webhook-token');
|
|
// reAuthAndGoHome already clears the request log before re-auth,
|
|
// so the log now contains all calls made during the re-auth process.
|
|
await browser.pause(3_000);
|
|
|
|
// Log all requests made during re-auth + startup for diagnostic purposes
|
|
const allRequests = getRequestLog();
|
|
|
|
// Check for any webhook-related requests in the log
|
|
const webhookCall = allRequests.find(
|
|
r => r.method === 'POST' && r.url.includes('/telegram/webhook')
|
|
);
|
|
const connectCall = allRequests.find(
|
|
r => r.method === 'GET' && r.url.includes('/auth/telegram/connect')
|
|
);
|
|
const skillsCall = allRequests.find(r => r.method === 'GET' && r.url.includes('/skills'));
|
|
|
|
console.log(
|
|
`${LOG_PREFIX} 7.4.1: Webhook call: ${!!webhookCall}, ` +
|
|
`Connect call: ${!!connectCall}, ` +
|
|
`Skills call: ${!!skillsCall}`
|
|
);
|
|
console.log(
|
|
`${LOG_PREFIX} 7.4.1: All requests after re-auth:`,
|
|
JSON.stringify(
|
|
allRequests.map(r => ({ method: r.method, url: r.url })),
|
|
null,
|
|
2
|
|
)
|
|
);
|
|
|
|
// Verify the app didn't crash — Home page should still be reachable
|
|
await navigateToHome();
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.4.1: App stable after webhook setup. Home: "${homeMarker}"`);
|
|
|
|
// Verify mock server received at least the authentication-related calls
|
|
// (login token consumption and /auth/me are always called on re-auth)
|
|
const authCall = allRequests.find(r => r.url.includes('/telegram/login-tokens'));
|
|
const meCall = allRequests.find(r => r.url.includes('/auth/me'));
|
|
expect(authCall || meCall).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.4.1: Auth calls confirmed in request log`);
|
|
|
|
console.log(`${LOG_PREFIX} 7.4.1 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 7.5 Disconnect & Re-Setup
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('7.5 Disconnect & Re-Setup', () => {
|
|
it('7.5.1 — Bot Unlink: Disconnect flow with confirmation dialog', async () => {
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-telegram-disconnect-token');
|
|
|
|
// Navigate to connections to find Telegram
|
|
const connectionsFound = await navigateToConnections();
|
|
if (!connectionsFound) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: Connections panel not reachable. ` +
|
|
`Attempting from Home page SkillsGrid.`
|
|
);
|
|
await navigateToHome();
|
|
}
|
|
|
|
await browser.pause(1_000);
|
|
|
|
// Open the Telegram modal
|
|
const modalState = await openTelegramModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: Telegram modal not opened — ` +
|
|
`skill may not be discovered in current environment. ` +
|
|
`Verifying disconnect endpoint is configured.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Telegram is not connected — disconnect test not applicable
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: Telegram not connected (showing setup wizard). ` +
|
|
`Disconnect test skipped — requires connected state.`
|
|
);
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Management panel is open — look for Disconnect button
|
|
expect(modalState).toBe('manage');
|
|
console.log(`${LOG_PREFIX} 7.5.1: Telegram management panel open`);
|
|
|
|
const hasDisconnectButton = await textExists('Disconnect');
|
|
|
|
if (!hasDisconnectButton) {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: "Disconnect" button not found. Tree:\n`,
|
|
tree.slice(0, 4000)
|
|
);
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Click "Disconnect" button
|
|
await clickText('Disconnect', 10_000);
|
|
console.log(`${LOG_PREFIX} 7.5.1: Clicked "Disconnect" button`);
|
|
await browser.pause(2_000);
|
|
|
|
// Verify confirmation dialog appears with Cancel + Confirm Disconnect
|
|
const hasCancel = await textExists('Cancel');
|
|
const hasConfirmDisconnect =
|
|
(await textExists('Confirm Disconnect')) || (await textExists('Confirm'));
|
|
|
|
if (hasCancel || hasConfirmDisconnect) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: Confirmation dialog appeared — ` +
|
|
`Cancel: ${hasCancel}, Confirm: ${hasConfirmDisconnect}`
|
|
);
|
|
expect(hasCancel || hasConfirmDisconnect).toBe(true);
|
|
|
|
// Click "Confirm Disconnect"
|
|
clearRequestLog();
|
|
if (await textExists('Confirm Disconnect')) {
|
|
await clickText('Confirm Disconnect', 10_000);
|
|
} else if (await textExists('Confirm')) {
|
|
await clickText('Confirm', 10_000);
|
|
}
|
|
console.log(`${LOG_PREFIX} 7.5.1: Clicked confirm disconnect`);
|
|
await browser.pause(3_000);
|
|
|
|
// Verify disconnect request was made to mock server
|
|
const disconnectCall = await waitForRequest('POST', '/telegram/disconnect', 5_000);
|
|
if (disconnectCall) {
|
|
console.log(`${LOG_PREFIX} 7.5.1: Disconnect API call confirmed`);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: No disconnect API call detected ` +
|
|
`(disconnect may be handled locally by Rust runtime)`
|
|
);
|
|
}
|
|
|
|
// After disconnect, the modal should close or show setup wizard
|
|
await browser.pause(2_000);
|
|
const hasConnectTitle = await textExists('Connect Telegram');
|
|
const hasManageTitle = await textExists('Manage Telegram');
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: After disconnect — Connect visible: ${hasConnectTitle}, ` +
|
|
`Manage visible: ${hasManageTitle}`
|
|
);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.1: Confirmation dialog not shown — ` +
|
|
`disconnect may have happened immediately`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.5.1 PASSED`);
|
|
});
|
|
|
|
it('7.5.3 — Re-Run Setup: setup wizard accessible after disconnect', async () => {
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-telegram-rerun-token');
|
|
|
|
// Navigate to connections
|
|
const connectionsFound = await navigateToConnections();
|
|
if (!connectionsFound) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.3: Connections panel not reachable. Trying Home SkillsGrid.`
|
|
);
|
|
await navigateToHome();
|
|
}
|
|
|
|
await browser.pause(1_000);
|
|
|
|
// Open Telegram modal
|
|
const modalState = await openTelegramModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.3: Telegram modal not opened — skill not discovered. Skipping.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Already in setup mode — setup wizard is accessible
|
|
const hasConnectTitle = await textExists('Connect Telegram');
|
|
expect(hasConnectTitle).toBe(true);
|
|
console.log(`${LOG_PREFIX} 7.5.3: Setup wizard accessible ("Connect Telegram" visible)`);
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.5.3 PASSED`);
|
|
return;
|
|
}
|
|
|
|
// Management panel is open — look for "Re-run Setup" button
|
|
expect(modalState).toBe('manage');
|
|
|
|
const hasReRunSetup =
|
|
(await textExists('Re-run Setup')) || (await textExists('Re-Run Setup'));
|
|
|
|
if (hasReRunSetup) {
|
|
const reRunText = (await textExists('Re-run Setup')) ? 'Re-run Setup' : 'Re-Run Setup';
|
|
await clickText(reRunText, 10_000);
|
|
console.log(`${LOG_PREFIX} 7.5.3: Clicked "${reRunText}" button`);
|
|
await browser.pause(2_000);
|
|
|
|
// Verify setup wizard appears
|
|
const hasConnectTitle = await textExists('Connect Telegram');
|
|
if (hasConnectTitle) {
|
|
expect(hasConnectTitle).toBe(true);
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.3: Setup wizard opened ("Connect Telegram" visible after Re-run Setup)`
|
|
);
|
|
} else {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.3: "Connect Telegram" not found after Re-run Setup. Tree:\n`,
|
|
tree.slice(0, 4000)
|
|
);
|
|
}
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.3: "Re-run Setup" button not found in management panel. ` +
|
|
`Management panel may not have this option in current version.`
|
|
);
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} 7.5.3: Management panel tree:\n`, tree.slice(0, 3000));
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.5.3 PASSED`);
|
|
});
|
|
|
|
it('7.5.4 — Permission Re-Sync: skill status refreshes after reconnect', async () => {
|
|
resetMockBehavior();
|
|
setMockBehavior('telegramPermission', 'admin');
|
|
setMockBehavior('telegramSkillStatus', 'installed');
|
|
setMockBehavior('telegramSetupComplete', 'true');
|
|
|
|
// Re-auth forces a fresh user/team fetch which re-syncs permissions.
|
|
// reAuthAndGoHome already clears the request log before re-auth,
|
|
// so the log captures all calls made during re-auth.
|
|
await reAuthAndGoHome('e2e-telegram-resync-token');
|
|
await browser.pause(3_000);
|
|
|
|
// Verify the app made auth calls (which trigger permission sync)
|
|
const allRequests = getRequestLog();
|
|
const meCall = allRequests.find(r => r.url.includes('/auth/me'));
|
|
const teamsCall = allRequests.find(r => r.url.includes('/teams'));
|
|
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.4: Post re-auth calls — /auth/me: ${!!meCall}, /teams: ${!!teamsCall}`
|
|
);
|
|
|
|
// At least one of the auth/sync calls should have been made
|
|
expect(meCall || teamsCall).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.5.4: Auth calls confirmed — permission sync triggered`);
|
|
|
|
// Navigate to Home and verify the app is in a good state
|
|
await navigateToHome();
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 7.5.4: Home page reached after re-sync: "${homeMarker}"`);
|
|
|
|
// Check if Telegram is visible with updated status
|
|
const telegramVisible = await findTelegramInUI();
|
|
if (telegramVisible) {
|
|
console.log(`${LOG_PREFIX} 7.5.4: Telegram visible after permission re-sync`);
|
|
// Verify the status is not an error state (connected/setup_required are OK)
|
|
const hasErrorState = (await textExists('Error')) && !(await textExists('Setup Required'));
|
|
if (hasErrorState) {
|
|
console.log(`${LOG_PREFIX} 7.5.4: Warning — Telegram showing error state after re-sync`);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.4: Telegram status looks healthy after permission re-sync`
|
|
);
|
|
}
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 7.5.4: Telegram not visible in UI — ` +
|
|
`V8 runtime may not have discovered skill. Permission re-sync via re-auth confirmed.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 7.5.4 PASSED`);
|
|
});
|
|
});
|
|
});
|