dify/cli/test/e2e/setup/env.ts

211 lines
8.4 KiB
TypeScript

/**
* E2E environment configuration.
*
* ── Edition modes ─────────────────────────────────────────────────────────
*
* Community Edition (CE) — default, set DIFY_E2E_EDITION=ce or leave unset.
* Required: DIFY_E2E_HOST, DIFY_E2E_EMAIL, DIFY_E2E_PASSWORD
* global-setup registers the account (idempotent), mints tokens, imports
* all DSL fixtures into the single workspace, and publishes apps.
*
* Enterprise Edition (EE) — set DIFY_E2E_EDITION=ee.
* Required: DIFY_E2E_HOST, DIFY_E2E_EMAIL, DIFY_E2E_PASSWORD
* The operator must pre-create two workspaces for the test account:
* primary → named "auto_test0"
* secondary → named "auto_test1"
* global-setup logs in, discovers the two workspaces by name, imports DSL
* fixtures into both, publishes apps, and sets access_mode → public.
*
* ── EE-only test cases ────────────────────────────────────────────────────
* Tests that require multiple workspaces or EE-specific features are tagged
* [EE] and wrapped with enterpriseOnlyIt() / enterpriseOnlyDescribe() from
* helpers/skip.ts. They are automatically skipped in CE mode.
*
* ── Optional env-var overrides (both editions) ────────────────────────────
* DIFY_E2E_TOKEN Pre-minted bearer token — skips device-flow mint
* DIFY_E2E_SSO_TOKEN External SSO bearer token (dfoe_ prefix)
* DIFY_E2E_CONSOLE_URL Console URL when different from DIFY_E2E_HOST
* DIFY_E2E_WORKSPACE_ID Override primary workspace ID
* DIFY_E2E_WORKSPACE_NAME Override primary workspace name
* DIFY_E2E_WS2_ID Override secondary workspace ID (EE)
* DIFY_E2E_WS2_APP_ID Override secondary workspace app ID (EE)
* DIFY_E2E_CHAT_APP_ID Override echo-chat app ID
* DIFY_E2E_WORKFLOW_APP_ID Override echo-workflow app ID
* DIFY_E2E_FILE_APP_ID Override file-upload app ID
* DIFY_E2E_FILE_CHAT_APP_ID Override file-chat app ID
* DIFY_E2E_HITL_APP_ID Override HITL main app ID
* DIFY_E2E_HITL_EXTERNAL_APP_ID
* DIFY_E2E_HITL_SINGLE_ACTION_APP_ID
* DIFY_E2E_HITL_MULTI_NODE_APP_ID
*/
/** Supported edition values. */
export type DifyEdition = 'ce' | 'ee'
export type E2EEnv = {
/** Staging server base URL (API endpoint) */
host: string
/**
* Edition: "ce" (Community Edition, default) or "ee" (Enterprise Edition).
* Controls which global-setup path runs and which test cases are active.
*/
edition: DifyEdition
/** Internal user bearer token (dfoa_…) */
token: string
/** External SSO bearer token (dfoe_…) — may be empty */
ssoToken: string
/** Primary workspace ID */
workspaceId: string
/** Workspace name (informational) */
workspaceName: string
/** Chat app that echoes the query */
chatAppId: string
/** Workflow app that echoes input x */
workflowAppId: string
/** Workflow app with HITL node (display_in_ui=true) — empty when not configured */
hitlAppId: string
/** Workflow app with HITL node (display_in_ui=false) — empty when not configured */
hitlExternalAppId: string
/** Workflow app with HITL node (display_in_ui=true, exactly 1 action) */
hitlSingleActionAppId: string
/** Workflow app with 2 serial Human-Input nodes */
hitlMultiNodeAppId: string
/** Workflow app with file input (doc variable) */
fileAppId: string
/** Chat app (advanced-chat) with a file input variable */
fileChatAppId: string
/**
* Secondary workspace ID — EE only ("auto_test1").
* Empty in CE mode (CE has a single workspace).
*/
ws2Id: string
/** App ID inside the secondary workspace — EE only. Empty in CE mode. */
ws2AppId: string
/** Console account email */
email: string
/** Console account password (plain-text; Base64-encoded before sending) */
password: string
/**
* Console URL — defaults to `host` when not set separately.
* Useful when the API host and the console host differ.
*/
consoleUrl: string
}
export type E2ECapabilities = {
tokenValid: boolean
tokenId?: string
/**
* Edition resolved by global-setup — "ce" or "ee".
* Injected into every test file so helpers/skip.ts can gate EE-only cases.
*/
edition: DifyEdition
/** Primary bearer token minted by global-setup via the device flow. */
token: string
/**
* Per-suite dedicated tokens — each destructive suite (logout, devices)
* gets its own fresh dfoa_ token so revoking it never kills the main token.
*/
logoutToken: string
devicesToken: string
/** Primary workspace info. */
workspaceId: string
workspaceName: string
/** Secondary workspace ID (EE only). Empty string in CE mode. */
ws2Id: string
/** App IDs resolved by provisionApps. Empty = fall back to env var. */
chatAppId: string
workflowAppId: string
fileAppId: string
fileChatAppId: string
hitlAppId: string
hitlExternalAppId: string
hitlSingleActionAppId: string
hitlMultiNodeAppId: string
ws2AppId: string
}
let _cached: E2EEnv | undefined
/** Return true when running in Enterprise Edition mode. */
export function isEnterpriseEdition(): boolean {
return (process.env.DIFY_E2E_EDITION ?? 'ce').toLowerCase() === 'ee'
}
/** Load and validate E2E environment variables. Throws if required vars are missing. */
export function loadE2EEnv(): E2EEnv {
if (_cached !== undefined)
return _cached
const edition: DifyEdition = isEnterpriseEdition() ? 'ee' : 'ce'
// Same 3 required vars for both CE and EE.
const required: Array<[keyof NodeJS.ProcessEnv, string]> = [
['DIFY_E2E_HOST', 'Staging server URL'],
['DIFY_E2E_EMAIL', 'Console account email'],
['DIFY_E2E_PASSWORD', 'Console account password'],
]
const missing = required.filter(([k]) => !process.env[k])
if (missing.length > 0) {
const list = missing.map(([k, desc]) => ` ${k} (${desc})`).join('\n')
throw new Error(
`E2E tests require the following environment variables to be set:\n${list}\n\n`
+ `Edition: ${edition.toUpperCase()}\n`
+ 'See test/e2e/setup/env.ts for documentation.',
)
}
_cached = {
host: process.env.DIFY_E2E_HOST!,
edition,
token: process.env.DIFY_E2E_TOKEN ?? '',
ssoToken: process.env.DIFY_E2E_SSO_TOKEN ?? '',
workspaceId: process.env.DIFY_E2E_WORKSPACE_ID ?? '',
workspaceName: process.env.DIFY_E2E_WORKSPACE_NAME ?? '',
chatAppId: process.env.DIFY_E2E_CHAT_APP_ID ?? '',
workflowAppId: process.env.DIFY_E2E_WORKFLOW_APP_ID ?? '',
hitlAppId: process.env.DIFY_E2E_HITL_APP_ID ?? '',
hitlExternalAppId: process.env.DIFY_E2E_HITL_EXTERNAL_APP_ID ?? '',
hitlSingleActionAppId: process.env.DIFY_E2E_HITL_SINGLE_ACTION_APP_ID ?? '',
hitlMultiNodeAppId: process.env.DIFY_E2E_HITL_MULTI_NODE_APP_ID ?? '',
fileAppId: process.env.DIFY_E2E_FILE_APP_ID ?? '',
fileChatAppId: process.env.DIFY_E2E_FILE_CHAT_APP_ID ?? '',
ws2Id: process.env.DIFY_E2E_WS2_ID ?? '',
ws2AppId: process.env.DIFY_E2E_WS2_APP_ID ?? '',
email: process.env.DIFY_E2E_EMAIL!,
password: process.env.DIFY_E2E_PASSWORD!,
consoleUrl: process.env.DIFY_E2E_CONSOLE_URL ?? process.env.DIFY_E2E_HOST!,
}
return _cached
}
export function isE2ELocalMode(): boolean {
return process.env.DIFY_E2E_MODE === 'local'
}
/**
* Resolve the E2E environment, merging capabilities (from global-setup) on top
* of the optional env-var overrides. Capabilities always take priority.
*/
export function resolveEnv(caps: E2ECapabilities): E2EEnv {
const env = loadE2EEnv()
return {
...env,
edition: caps.edition || env.edition,
token: caps.token || env.token,
workspaceId: caps.workspaceId || env.workspaceId,
workspaceName: caps.workspaceName || env.workspaceName,
ws2Id: caps.ws2Id || env.ws2Id,
chatAppId: caps.chatAppId || env.chatAppId,
workflowAppId: caps.workflowAppId || env.workflowAppId,
fileAppId: caps.fileAppId || env.fileAppId,
fileChatAppId: caps.fileChatAppId || env.fileChatAppId,
hitlAppId: caps.hitlAppId || env.hitlAppId,
hitlExternalAppId: caps.hitlExternalAppId || env.hitlExternalAppId,
hitlSingleActionAppId: caps.hitlSingleActionAppId || env.hitlSingleActionAppId,
hitlMultiNodeAppId: caps.hitlMultiNodeAppId || env.hitlMultiNodeAppId,
ws2AppId: caps.ws2AppId || env.ws2AppId,
}
}