mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-24 17:35:19 +08:00
Merge pull request #2700 from langbot-app/test/quality-workspace-ui-oracles-20261017
test(web): replace Workspace source checks with runtime assertions
This commit is contained in:
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import ts from 'typescript';
|
||||
|
||||
const root = process.cwd();
|
||||
const dialogPath = path.join(
|
||||
@@ -12,43 +13,30 @@ const localeDir = path.join(root, 'src/i18n/locales');
|
||||
const backendUrlPath = path.join(root, 'src/app/infra/http/backendUrl.ts');
|
||||
|
||||
const dialogSource = fs.readFileSync(dialogPath, 'utf8');
|
||||
const backendUrlSource = fs.readFileSync(backendUrlPath, 'utf8');
|
||||
const { resolveBackendBaseUrl } = await import(
|
||||
`data:text/javascript;base64,${Buffer.from(
|
||||
ts.transpileModule(fs.readFileSync(backendUrlPath, 'utf8'), {
|
||||
compilerOptions: {
|
||||
module: ts.ModuleKind.ESNext,
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
},
|
||||
}).outputText,
|
||||
).toString('base64')}`
|
||||
);
|
||||
|
||||
test('QR login resolves a proxy-root API base against the current origin', () => {
|
||||
assert.match(dialogSource, /const baseUrl = getBackendBaseUrl\(\)/);
|
||||
assert.match(backendUrlSource, /!configured \|\| configured === '\/'/);
|
||||
assert.match(backendUrlSource, /return normalizedOrigin/);
|
||||
assert.doesNotMatch(
|
||||
dialogSource,
|
||||
/import\.meta\.env\.VITE_API_BASE_URL \|\| window\.location\.origin/,
|
||||
);
|
||||
});
|
||||
|
||||
test('QR credential exchanges preserve the active Workspace scope', () => {
|
||||
assert.match(dialogSource, /getActiveWorkspaceUuid/);
|
||||
assert.match(
|
||||
dialogSource,
|
||||
/sessionWorkspaceUuidRef\.current = workspaceUuid/,
|
||||
);
|
||||
assert.match(
|
||||
dialogSource,
|
||||
/const workspaceUuid = sessionWorkspaceUuidRef\.current/,
|
||||
);
|
||||
assert.match(dialogSource, /sessionApiBaseRef\.current = cfg\.apiBase/);
|
||||
assert.match(
|
||||
dialogSource,
|
||||
/`\$\{baseUrlRef\.current\}\$\{sessionApiBaseRef\.current\}\/\$\{sessionIdRef\.current\}`/,
|
||||
);
|
||||
assert.match(dialogSource, /'X-Workspace-Id': workspaceUuid/);
|
||||
|
||||
const workspaceHeaderUses = dialogSource.match(
|
||||
/'X-Workspace-Id': workspaceUuid/g,
|
||||
);
|
||||
assert.equal(
|
||||
workspaceHeaderUses?.length,
|
||||
4,
|
||||
'start, poll, expiry cleanup, and dialog cleanup must all retain Workspace scope',
|
||||
);
|
||||
for (const [configured, expected] of [
|
||||
[undefined, 'https://core.example'],
|
||||
['', 'https://core.example'],
|
||||
['/', 'https://core.example'],
|
||||
['/proxy/', 'https://core.example/proxy'],
|
||||
['proxy', 'https://core.example/proxy'],
|
||||
['https://api.example/root/', 'https://api.example/root'],
|
||||
])
|
||||
assert.equal(
|
||||
resolveBackendBaseUrl(configured, 'https://core.example///'),
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
test('WeChat QR login never reuses Feishu progress copy', () => {
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import test from 'node:test';
|
||||
|
||||
const source = fs.readFileSync(
|
||||
new URL('../../src/app/auth/space/callback/page.tsx', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('direct launch assertion is fragment-only and removed before exchange', () => {
|
||||
assert.doesNotMatch(source, /searchParams\.get\(['"]launch_assertion['"]\)/);
|
||||
const readIndex = source.indexOf("fragmentParams.get('launch_assertion')");
|
||||
const clearIndex = source.indexOf('window.history.replaceState');
|
||||
const exchangeIndex = source.indexOf('handleOAuthCallback(', clearIndex);
|
||||
assert.ok(readIndex >= 0, 'fragment assertion read is missing');
|
||||
assert.ok(
|
||||
clearIndex > readIndex,
|
||||
'URL fragment is not cleared after copying the assertion',
|
||||
);
|
||||
assert.ok(
|
||||
exchangeIndex > clearIndex,
|
||||
'assertion exchange starts before the fragment is cleared',
|
||||
);
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const panelSource = await readFile(
|
||||
new URL(
|
||||
'../../src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx',
|
||||
import.meta.url,
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const entitySource = await readFile(
|
||||
new URL('../../src/app/infra/entities/workspace.ts', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('workspace member rows show display name, email, and role', () => {
|
||||
assert.match(entitySource, /display_name:\s*string/);
|
||||
assert.match(panelSource, /\{member\.display_name\}/);
|
||||
assert.match(
|
||||
panelSource,
|
||||
/<ItemDescription[^>]*>[\s\S]*?\{member\.email\}[\s\S]*?workspace\.roles\.\$\{member\.role\}[\s\S]*?<\/ItemDescription>/,
|
||||
);
|
||||
});
|
||||
@@ -18,103 +18,15 @@ const homeSidebarSource = readSource(
|
||||
const workspaceSettingsPanelSource = readSource(
|
||||
'src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx',
|
||||
);
|
||||
const workspaceSwitcherSource = readSource(
|
||||
'src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx',
|
||||
);
|
||||
|
||||
test('renders WorkspaceSwitcher only from HomeSidebar', () => {
|
||||
assert.doesNotMatch(homeLayoutSource, /<WorkspaceSwitcher\b/);
|
||||
assert.doesNotMatch(workspaceSettingsPanelSource, /<WorkspaceSwitcher\b/);
|
||||
assert.equal(homeSidebarSource.match(/<WorkspaceSwitcher\b/g)?.length, 1);
|
||||
});
|
||||
|
||||
test('places WorkspaceSwitcher between the sidebar header and Home navigation', () => {
|
||||
const headerEnd = homeSidebarSource.indexOf('</SidebarHeader>');
|
||||
const switcher = homeSidebarSource.indexOf('<WorkspaceSwitcher');
|
||||
const contentStart = homeSidebarSource.indexOf('<SidebarContent');
|
||||
const homeGroup = homeSidebarSource.indexOf(
|
||||
"<SidebarGroupLabel>{t('sidebar.home')}</SidebarGroupLabel>",
|
||||
);
|
||||
|
||||
assert.notEqual(headerEnd, -1);
|
||||
assert.notEqual(switcher, -1);
|
||||
assert.notEqual(contentStart, -1);
|
||||
assert.notEqual(homeGroup, -1);
|
||||
assert.ok(
|
||||
headerEnd < switcher,
|
||||
'WorkspaceSwitcher must follow SidebarHeader',
|
||||
);
|
||||
assert.ok(
|
||||
switcher < contentStart && switcher < homeGroup,
|
||||
'WorkspaceSwitcher must precede SidebarContent and the Home group',
|
||||
);
|
||||
});
|
||||
|
||||
test('hides WorkspaceSwitcher for the singleton local OSS workspace and keeps it for Cloud or multiple workspaces', () => {
|
||||
assert.match(
|
||||
workspaceSwitcherSource,
|
||||
/if \(!currentWorkspace\) return null;/,
|
||||
);
|
||||
assert.match(
|
||||
homeSidebarSource,
|
||||
/const workspaces = useWorkspaceBootstrap\(\);/,
|
||||
);
|
||||
assert.match(
|
||||
homeSidebarSource,
|
||||
/const showWorkspaceSwitcher\s*=\s*workspaces\.length\s*>\s*1\s*\|\|\s*currentWorkspace\?\.workspace\.source\s*===\s*'cloud_projection'/,
|
||||
);
|
||||
assert.match(
|
||||
homeSidebarSource,
|
||||
/\{showWorkspaceSwitcher\s*&&\s*\(\s*<div className="px-2[^>]*>[\s\S]*?<WorkspaceSwitcher/,
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps Cloud workspace member management in Workspace Settings', () => {
|
||||
assert.match(workspaceSettingsPanelSource, /workspace\.inviteMember/);
|
||||
assert.doesNotMatch(workspaceSettingsPanelSource, /#workspace-members/);
|
||||
assert.doesNotMatch(workspaceSettingsPanelSource, /cloudMembersURL/);
|
||||
assert.doesNotMatch(workspaceSettingsPanelSource, /canManageCloudMembers/);
|
||||
});
|
||||
|
||||
test('keeps workspace plan and settings controls on the workspace row', () => {
|
||||
assert.match(workspaceSwitcherSource, /entry\.plan_name/);
|
||||
assert.match(
|
||||
workspaceSwitcherSource,
|
||||
/aria-label=\{t\('workspace\.settings'\)\}/,
|
||||
);
|
||||
assert.match(workspaceSwitcherSource, /className="size-8"/);
|
||||
assert.doesNotMatch(workspaceSwitcherSource, /workspace\.currentPlan/);
|
||||
assert.doesNotMatch(workspaceSwitcherSource, /workspace\.upgradePlan/);
|
||||
assert.doesNotMatch(workspaceSwitcherSource, /workspace\.roles/);
|
||||
assert.doesNotMatch(workspaceSwitcherSource, /entry\.membership\.role/);
|
||||
});
|
||||
|
||||
test('moves Cloud plan upgrades into Workspace Settings', () => {
|
||||
assert.match(workspaceSettingsPanelSource, /cloudPlans\.title/);
|
||||
assert.match(workspaceSettingsPanelSource, /<CloudPlansDialog/);
|
||||
assert.match(workspaceSettingsPanelSource, /systemInfo\.cloud_service_url/);
|
||||
});
|
||||
|
||||
test('aligns the workspace trigger with navigation entries on both sides and truncates long names', () => {
|
||||
assert.match(
|
||||
homeSidebarSource,
|
||||
/<div className="px-2[^>]*>[\s\S]*<WorkspaceSwitcher className="w-full/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
homeSidebarSource,
|
||||
/WorkspaceSwitcher className="[^"]*w-4\/5/,
|
||||
);
|
||||
assert.match(workspaceSwitcherSource, /h-9/);
|
||||
assert.match(workspaceSwitcherSource, /w-64/);
|
||||
assert.doesNotMatch(workspaceSwitcherSource, /min-w-80/);
|
||||
assert.match(workspaceSwitcherSource, /max-w-\[7rem\][^>]*truncate/);
|
||||
});
|
||||
|
||||
test('uses an infrastructure-level Box health endpoint in monitoring', () => {
|
||||
const statusCardSource = readSource(
|
||||
'src/app/home/monitoring/components/overview-cards/SystemStatusCards.tsx',
|
||||
);
|
||||
const backendClientSource = readSource('src/app/infra/http/BackendClient.ts');
|
||||
assert.match(backendClientSource, /getBoxRuntimeStatus/);
|
||||
assert.match(statusCardSource, /getBoxRuntimeStatus/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user