mirror of
https://github.com/langgenius/dify.git
synced 2026-06-10 18:24:09 +08:00
191 lines
9.9 KiB
Markdown
191 lines
9.9 KiB
Markdown
# Dify CLI — E2E Test Suite
|
|
|
|
End-to-end tests that exercise the **real `difyctl` binary** against a live
|
|
Dify server. Every test uses an isolated temporary config directory so no
|
|
state leaks between test files.
|
|
|
|
## Directory layout
|
|
|
|
```
|
|
test/e2e/
|
|
├── setup/
|
|
│ ├── env.ts — Load & validate DIFY_E2E_* env vars (CE + EE)
|
|
│ ├── global-setup.ts — CE/EE-aware bootstrap: account creation, token
|
|
│ │ minting, workspace provisioning, DSL import
|
|
│ └── global-teardown.ts — Delete conversations created during the run
|
|
│
|
|
├── helpers/
|
|
│ ├── cli.ts — run(), withAuthFixture(), mintFreshToken(),
|
|
│ │ injectAuth(), spawn_background()
|
|
│ ├── assert.ts — assertExitCode, assertJson, assertErrorEnvelope,
|
|
│ │ assertNoAnsi, assertPipeFriendlyJson, ...
|
|
│ ├── cleanup-registry.ts — registerConversation() / cleanupRegisteredConversations()
|
|
│ ├── retry.ts — withRetry(fn, { attempts, delayMs })
|
|
│ └── skip.ts — optionalIt(), optionalDescribe(),
|
|
│ enterpriseOnlyIt(), enterpriseOnlyDescribe(), isEE()
|
|
│
|
|
└── suites/
|
|
├── auth/
|
|
│ ├── status.e2e.ts — auth status (text + JSON + SSO)
|
|
│ ├── use.e2e.ts — workspace switching ([EE] cases require 2 workspaces)
|
|
│ ├── whoami.e2e.ts — whoami + external SSO session checks
|
|
│ ├── devices.e2e.ts — devices list + revoke (runs near-last)
|
|
│ └── logout.e2e.ts — logout + local credential cleanup (runs last)
|
|
├── config/
|
|
│ └── config.e2e.ts — config path/get/set/unset/view, env override
|
|
├── discovery/
|
|
│ ├── get-app-list.e2e.ts — basic get app list
|
|
│ ├── get-app-single.e2e.ts — get single app by ID
|
|
│ ├── describe-app.e2e.ts — describe app
|
|
│ └── get-app-all-workspaces.e2e.ts — get app -A ([EE] multi-workspace cases)
|
|
└── run/
|
|
├── run-app-basic.e2e.ts — basic run, -o json, --inputs, streaming,
|
|
│ conversation, CI mode
|
|
├── run-app-streaming.e2e.ts — Ctrl+C / error-event / chunk timing
|
|
├── run-app-file.e2e.ts — --file upload (local + remote URL)
|
|
└── run-app-hitl.e2e.ts — HITL pause + resume
|
|
```
|
|
|
|
## Edition support
|
|
|
|
`difyctl` supports two Dify editions. The test suite adapts automatically:
|
|
|
|
| Edition | `DIFY_E2E_EDITION` | Workspaces | EE-only cases |
|
|
| ----------------------- | ------------------ | ---------------- | ------------- |
|
|
| Community Edition (CE) | `ce` (default) | 1 | Skipped |
|
|
| Enterprise Edition (EE) | `ee` | 2 (auto-created) | Active |
|
|
|
|
### EE-only test cases
|
|
|
|
Tests that require Enterprise Edition features (workspace switching between
|
|
independent workspaces, cross-workspace app query, etc.) are tagged `[EE]`
|
|
in their names and wrapped with `enterpriseOnlyIt()` / `enterpriseOnlyDescribe()`
|
|
from `helpers/skip.ts`. In CE mode these tests are automatically skipped.
|
|
|
|
```ts
|
|
// helpers/skip.ts usage
|
|
const eeIt = enterpriseOnlyIt(caps)
|
|
eeIt('[EE][P0] cross-workspace query returns apps from all workspaces', async () => {
|
|
// test body
|
|
})
|
|
```
|
|
|
|
## Setup
|
|
|
|
Copy the credential template and fill in your values:
|
|
|
|
```bash
|
|
cp cli/test/e2e/.env.e2e.example cli/.env.e2e
|
|
# edit cli/.env.e2e with real credentials
|
|
```
|
|
|
|
### Community Edition (CE) — minimum 3 vars
|
|
|
|
| Variable | Description |
|
|
| ------------------- | ----------------------------------------------------- |
|
|
| `DIFY_E2E_HOST` | Server base URL (`http://localhost`) |
|
|
| `DIFY_E2E_EMAIL` | Account email — created automatically by global-setup |
|
|
| `DIFY_E2E_PASSWORD` | Account password |
|
|
|
|
global-setup will:
|
|
|
|
1. Register the account (idempotent — safe to rerun)
|
|
1. Login and mint a bearer token via the device flow
|
|
1. Import all DSL fixtures into the single workspace
|
|
1. Publish apps and set access_mode → public
|
|
|
|
### Enterprise Edition (EE) — 5 required vars
|
|
|
|
| Variable | Description |
|
|
| ------------------------------------ | ------------------------------------------------------- |
|
|
| `DIFY_E2E_EDITION` | Must be `ee` |
|
|
| `DIFY_E2E_HOST` | Console/API base URL |
|
|
| `DIFY_E2E_EMAIL` | Member account email — created via enterprise API |
|
|
| `DIFY_E2E_PASSWORD` | Member account password |
|
|
| `DIFY_E2E_ENTERPRISE_API_URL` | Enterprise admin API base URL (`https://.../inner/api`) |
|
|
| `DIFY_E2E_ENTERPRISE_API_SECRET_KEY` | Enterprise admin API secret key |
|
|
|
|
Optional:
|
|
|
|
| Variable | Description |
|
|
| ---------------------- | --------------------------------------------- |
|
|
| `DIFY_E2E_CONSOLE_URL` | Console URL if different from `DIFY_E2E_HOST` |
|
|
|
|
global-setup will:
|
|
|
|
1. Create the member account via the enterprise admin API (idempotent)
|
|
1. Login and obtain a session cookie
|
|
1. Create two workspaces (`e2e-primary-auto`, `e2e-secondary-auto`) via the enterprise API
|
|
1. Import DSL fixtures into both workspaces
|
|
1. Publish apps and set access_mode → public via the enterprise API
|
|
|
|
### Optional overrides (both editions)
|
|
|
|
| Variable | Description |
|
|
| ------------------------------------ | ------------------------------------------------ |
|
|
| `DIFY_E2E_TOKEN` | Pre-minted bearer token — skips device-flow mint |
|
|
| `DIFY_E2E_SSO_TOKEN` | External SSO bearer token (`dfoe_...`) |
|
|
| `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_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` | |
|
|
| `DIFY_E2E_WS2_APP_ID` | Override secondary workspace app ID (EE) |
|
|
|
|
## Running tests
|
|
|
|
```bash
|
|
cd cli
|
|
|
|
# Community Edition (default)
|
|
bun run test:e2e
|
|
|
|
# Enterprise Edition
|
|
DIFY_E2E_EDITION=ee bun run test:e2e
|
|
|
|
# Run only [P0] smoke cases
|
|
bun run test:e2e:smoke
|
|
|
|
# Run only EE-tagged cases (P0 smoke)
|
|
DIFY_E2E_EDITION=ee bun run test:e2e:smoke --testNamePattern "\[EE\]"
|
|
|
|
# Run offline-safe config tests only (no network required)
|
|
bun run test:e2e:local
|
|
|
|
# Run a single file
|
|
bun vitest --config vitest.e2e.config.ts test/e2e/suites/auth/status.e2e.ts
|
|
```
|
|
|
|
## Test execution order
|
|
|
|
Files run sequentially (`fileParallelism: false`) in this order:
|
|
|
|
```
|
|
login → status → use → whoami → help → config → output → error-handling
|
|
→ framework → discovery → run (basic / streaming / file / HITL)
|
|
→ devices → logout
|
|
```
|
|
|
|
`devices` and `logout` run last because they revoke real server sessions.
|
|
|
|
## Design decisions
|
|
|
|
| Decision | Rationale |
|
|
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
| **CE/EE edition flag** | `DIFY_E2E_EDITION=ce/ee` controls global-setup bootstrap path and activates/skips `[EE]`-tagged tests. |
|
|
| **`[EE]` tag convention** | Test names include `[EE]` to make skipped cases visible in the report and to allow `--testNamePattern "\[EE\]"` filtering. |
|
|
| **`enterpriseOnlyIt(caps)`** | Returns `it` in EE mode, `it.skip` in CE mode — no runtime assertions needed, skip is declarative. |
|
|
| **No mocking** | All HTTP traffic goes to the real server — this catches real integration regressions. |
|
|
| **Isolated config dirs** | Each test creates a fresh `withTempConfig()` dir; session state never leaks between tests. |
|
|
| **`withAuthFixture()`** | Combines `withTempConfig` + `injectAuth` into a single fixture; reduces beforeEach boilerplate. |
|
|
| **`injectAuth()` bypasses Device Flow** | Non-auth tests skip the browser step; only `auth/` suites exercise the real flow. |
|
|
| **`mintFreshToken()`** | `logout` and `devices-revoke` tests mint a disposable `dfoa_` token via the device flow API. |
|
|
| **Global `retry: 0`** | Flaky network calls use `withRetry()` locally; global retry masks non-idempotent failures. |
|
|
| **Conversation cleanup** | `registerConversation()` + global-teardown delete staging conversations after the run. |
|