From 19d2a4d7a05c47a14a94dd011224c208faa07300 Mon Sep 17 00:00:00 2001 From: Yunlu Wen Date: Tue, 9 Jun 2026 18:06:55 +0800 Subject: [PATCH] fix: run ci properly on pr (#37233) --- .github/workflows/cli-tests.yml | 7 ++- cli/src/commands/auth/login/login.test.ts | 2 +- cli/src/commands/create/member/run.test.ts | 16 +++---- cli/src/commands/delete/member/run.test.ts | 12 ++--- cli/src/commands/get/app/run.test.ts | 8 ++-- cli/src/commands/get/member/run.test.ts | 18 +++---- cli/src/commands/get/workspace/run.test.ts | 24 +++++----- cli/src/commands/set/member/run.test.ts | 12 ++--- .../commands/skills/install/registry.test.ts | 18 +++---- cli/src/store/store.ts | 5 ++ cli/test/e2e/helpers/cleanup-registry.ts | 4 +- cli/test/e2e/setup/global-setup.ts | 16 +++---- cli/test/fixtures/dify-mock/scenarios.ts | 14 +++--- cli/test/fixtures/dify-mock/server.test.ts | 16 +++---- cli/test/fixtures/dify-mock/server.ts | 8 ++-- cli/vitest.e2e.config.ts | 48 +++++++++---------- 16 files changed, 120 insertions(+), 108 deletions(-) diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 4e696430af..d27ab7db54 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -37,12 +37,17 @@ jobs: - name: Setup web environment uses: ./.github/actions/setup-web + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: Validate release manifest if: matrix.os == 'depot-ubuntu-24.04' run: scripts/release-validate-manifest.sh - name: CI pipeline (typecheck, lint, coverage, build) - run: pnpm ci + run: pnpm run ci - name: Report coverage if: ${{ env.CODECOV_TOKEN != '' && matrix.os == 'depot-ubuntu-24.04' }} diff --git a/cli/src/commands/auth/login/login.test.ts b/cli/src/commands/auth/login/login.test.ts index d4dab0bf28..bd8b7ef6e1 100644 --- a/cli/src/commands/auth/login/login.test.ts +++ b/cli/src/commands/auth/login/login.test.ts @@ -74,7 +74,7 @@ describe('runLogin', () => { }) const active = reg.resolveActive() expect(active?.ctx.account.email).toBe('tester@dify.ai') - expect(active?.ctx.workspace?.id).toBe('ws-1') + expect(active?.ctx.workspace?.id).toBe('550e8400-e29b-41d4-a716-446655440000') expect(active?.ctx.available_workspaces).toHaveLength(2) expect(store.get(tokenKey(active!.host, 'tester@dify.ai'))).toBe('dfoa_test') diff --git a/cli/src/commands/create/member/run.test.ts b/cli/src/commands/create/member/run.test.ts index 911319aeb9..f3fc75a84b 100644 --- a/cli/src/commands/create/member/run.test.ts +++ b/cli/src/commands/create/member/run.test.ts @@ -10,8 +10,8 @@ function active(): ActiveContext { email: 'inviter@example.com', ctx: { account: { id: 'acct-1', email: 'inviter@example.com', name: 'Inviter' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, - available_workspaces: [{ id: 'ws-1', name: 'Default', role: 'owner' }], + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + available_workspaces: [{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }], }, } } @@ -25,7 +25,7 @@ function fakeClient() { role: body.role, member_id: 'acct-new', invite_url: 'https://console.example.com/activate?email=x&token=tok', - tenant_id: 'ws-1', + tenant_id: '550e8400-e29b-41d4-a716-446655440000', })), } } @@ -42,7 +42,7 @@ describe('runCreateMember', () => { membersFactory: () => client as never, }, ) - expect(client.invite).toHaveBeenCalledWith('ws-1', { email: 'new@example.com', role: 'normal' }) + expect(client.invite).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000', { email: 'new@example.com', role: 'normal' }) expect(result.data.text()).toMatch(/Invited new@example\.com as normal/) expect(result.data.name()).toBe('acct-new') expect(result.data.json()).toMatchObject({ @@ -50,9 +50,9 @@ describe('runCreateMember', () => { role: 'normal', member_id: 'acct-new', invite_url: 'https://console.example.com/activate?email=x&token=tok', - tenant_id: 'ws-1', + tenant_id: '550e8400-e29b-41d4-a716-446655440000', }) - expect(result.workspaceId).toBe('ws-1') + expect(result.workspaceId).toBe('550e8400-e29b-41d4-a716-446655440000') }) it('rejects unknown role before any HTTP call', async () => { @@ -90,7 +90,7 @@ describe('runCreateMember', () => { it('-w flag overrides resolved workspace', async () => { const client = fakeClient() await runCreateMember( - { email: 'new@example.com', role: 'admin', workspace: 'ws-9' }, + { email: 'new@example.com', role: 'admin', workspace: '550e8400-e29b-41d4-a716-446655440008' }, { active: active(), http: {} as HttpClient, @@ -98,6 +98,6 @@ describe('runCreateMember', () => { membersFactory: () => client as never, }, ) - expect(client.invite).toHaveBeenCalledWith('ws-9', { email: 'new@example.com', role: 'admin' }) + expect(client.invite).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440008', { email: 'new@example.com', role: 'admin' }) }) }) diff --git a/cli/src/commands/delete/member/run.test.ts b/cli/src/commands/delete/member/run.test.ts index 696414798c..3476c2bb6c 100644 --- a/cli/src/commands/delete/member/run.test.ts +++ b/cli/src/commands/delete/member/run.test.ts @@ -10,8 +10,8 @@ function active(): ActiveContext { email: 'me@example.com', ctx: { account: { id: 'acct-1', email: 'me@example.com', name: 'Me' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, - available_workspaces: [{ id: 'ws-1', name: 'Default', role: 'owner' }], + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + available_workspaces: [{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }], }, } } @@ -34,17 +34,17 @@ describe('runDeleteMember', () => { membersFactory: () => client as never, }, ) - expect(client.remove).toHaveBeenCalledExactlyOnceWith('ws-1', 'acct-2') + expect(client.remove).toHaveBeenCalledExactlyOnceWith('550e8400-e29b-41d4-a716-446655440000', 'acct-2') expect(result.data.text()).toMatch(/Removed acct-2/) expect(result.data.name()).toBe('acct-2') expect(result.data.json()).toEqual({ id: 'acct-2', deleted: true }) - expect(result.workspaceId).toBe('ws-1') + expect(result.workspaceId).toBe('550e8400-e29b-41d4-a716-446655440000') }) it('-w flag overrides resolved workspace', async () => { const client = fakeClient() await runDeleteMember( - { memberId: 'acct-2', workspace: 'ws-9' }, + { memberId: 'acct-2', workspace: '550e8400-e29b-41d4-a716-446655440008' }, { active: active(), http: {} as HttpClient, @@ -52,7 +52,7 @@ describe('runDeleteMember', () => { membersFactory: () => client as never, }, ) - expect(client.remove).toHaveBeenCalledWith('ws-9', 'acct-2') + expect(client.remove).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440008', 'acct-2') }) it('rejects empty member id before any HTTP call', async () => { diff --git a/cli/src/commands/get/app/run.test.ts b/cli/src/commands/get/app/run.test.ts index f99eca3db4..9f11e30c48 100644 --- a/cli/src/commands/get/app/run.test.ts +++ b/cli/src/commands/get/app/run.test.ts @@ -12,10 +12,10 @@ const baseActive: ActiveContext = { email: 'tester@dify.ai', ctx: { account: { id: 'acct-1', email: 'tester@dify.ai', name: 'Test Tester' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, available_workspaces: [ - { id: 'ws-1', name: 'Default', role: 'owner' }, - { id: 'ws-2', name: 'Other', role: 'normal' }, + { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Other', role: 'normal' }, ], }, scheme: 'http', @@ -128,7 +128,7 @@ describe('runGetApp', () => { }) it('--workspace flag overrides bundle default', async () => { - const out = await render({ workspace: 'ws-2' }) + const out = await render({ workspace: '550e8400-e29b-41d4-a716-446655440001' }) expect(out).toContain('app-3') expect(out).toContain('OtherWS Bot') expect(out).not.toContain('Greeter') diff --git a/cli/src/commands/get/member/run.test.ts b/cli/src/commands/get/member/run.test.ts index 883cf6fafd..1dc2da2a36 100644 --- a/cli/src/commands/get/member/run.test.ts +++ b/cli/src/commands/get/member/run.test.ts @@ -11,8 +11,8 @@ function active(): ActiveContext { email: 'me@example.com', ctx: { account: { id: 'acct-1', email: 'me@example.com', name: 'Me' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, - available_workspaces: [{ id: 'ws-1', name: 'Default', role: 'owner' }], + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + available_workspaces: [{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }], }, } } @@ -44,8 +44,8 @@ describe('runGetMember', () => { membersFactory: () => client as never, }, ) - expect(client.list).toHaveBeenCalledExactlyOnceWith('ws-1', { page: 1, limit: 20 }) - expect(r.workspaceId).toBe('ws-1') + expect(client.list).toHaveBeenCalledExactlyOnceWith('550e8400-e29b-41d4-a716-446655440000', { page: 1, limit: 20 }) + expect(r.workspaceId).toBe('550e8400-e29b-41d4-a716-446655440000') expect(r.data.rows.map(row => row.current)).toEqual([true, false]) expect(r.data.rows.map(row => row.id)).toEqual(['acct-1', 'acct-2']) }) @@ -53,7 +53,7 @@ describe('runGetMember', () => { it('-w flag overrides resolved workspace', async () => { const client = fakeClient(env) const r = await runGetMember( - { workspace: 'ws-9' }, + { workspace: '550e8400-e29b-41d4-a716-446655440008' }, { active: active(), http: {} as HttpClient, @@ -61,8 +61,8 @@ describe('runGetMember', () => { membersFactory: () => client as never, }, ) - expect(client.list).toHaveBeenCalledWith('ws-9', { page: 1, limit: 20 }) - expect(r.workspaceId).toBe('ws-9') + expect(client.list).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440008', { page: 1, limit: 20 }) + expect(r.workspaceId).toBe('550e8400-e29b-41d4-a716-446655440008') }) it('--page/--limit are forwarded to the client', async () => { @@ -76,7 +76,7 @@ describe('runGetMember', () => { membersFactory: () => client as never, }, ) - expect(client.list).toHaveBeenCalledWith('ws-1', { page: 3, limit: 50 }) + expect(client.list).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000', { page: 3, limit: 50 }) }) it('marks no row when active context has no account id', async () => { @@ -86,7 +86,7 @@ describe('runGetMember', () => { email: 'me@example.com', ctx: { account: { id: '', email: '', name: '' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, }, } const r = await runGetMember( diff --git a/cli/src/commands/get/workspace/run.test.ts b/cli/src/commands/get/workspace/run.test.ts index 659888f1f4..7dd36e935a 100644 --- a/cli/src/commands/get/workspace/run.test.ts +++ b/cli/src/commands/get/workspace/run.test.ts @@ -12,10 +12,10 @@ const baseActive: ActiveContext = { email: 'tester@dify.ai', ctx: { account: { id: 'acct-1', email: 'tester@dify.ai', name: 'Test Tester' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, available_workspaces: [ - { id: 'ws-1', name: 'Default', role: 'owner' }, - { id: 'ws-2', name: 'Other', role: 'normal' }, + { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Other', role: 'normal' }, ], }, scheme: 'http', @@ -49,8 +49,8 @@ describe('runGetWorkspace', () => { it('default format renders ID NAME ROLE STATUS CURRENT table', async () => { const out = await render() expect(out).toMatch(/^ID\s+NAME\s+ROLE\s+STATUS\s+CURRENT/) - expect(out).toContain('ws-1') - expect(out).toContain('ws-2') + expect(out).toContain('550e8400-e29b-41d4-a716-446655440000') + expect(out).toContain('550e8400-e29b-41d4-a716-446655440001') expect(out).toContain('Default') expect(out).toContain('owner') expect(out).toContain('normal') @@ -69,18 +69,18 @@ describe('runGetWorkspace', () => { it('marks the current workspace with *', async () => { const out = await render() for (const line of out.split('\n')) { - if (line.includes('ws-1')) + if (line.includes('550e8400-e29b-41d4-a716-446655440000')) expect(line).toContain('*') - else if (line.includes('ws-2')) + else if (line.includes('550e8400-e29b-41d4-a716-446655440001')) expect(line).not.toContain('*') } }) it('falls back to active context workspace.id when server current=false', async () => { - const overridden: ActiveContext = { ...baseActive, ctx: { ...baseActive.ctx, workspace: { id: 'ws-2', name: 'Other', role: 'normal' } } } + const overridden: ActiveContext = { ...baseActive, ctx: { ...baseActive.ctx, workspace: { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Other', role: 'normal' } } } const out = await render('', overridden) for (const line of out.split('\n')) { - if (line.includes('ws-2')) + if (line.includes('550e8400-e29b-41d4-a716-446655440001')) expect(line).toContain('*') } }) @@ -89,7 +89,7 @@ describe('runGetWorkspace', () => { const out = await render('json') const parsed = JSON.parse(out) as { workspaces: Array<{ id: string, status: string, current: boolean }> } expect(parsed.workspaces).toHaveLength(2) - expect(parsed.workspaces.map(w => w.id).sort()).toEqual(['ws-1', 'ws-2']) + expect(parsed.workspaces.map(w => w.id).sort()).toEqual(['550e8400-e29b-41d4-a716-446655440000', '550e8400-e29b-41d4-a716-446655440001']) expect(parsed.workspaces[0]?.status).toBe('normal') expect(parsed.workspaces[0]?.current).toBe(true) }) @@ -97,12 +97,12 @@ describe('runGetWorkspace', () => { it('-o yaml emits "workspaces:" header', async () => { const out = await render('yaml') expect(out).toContain('workspaces:') - expect(out).toContain('ws-1') + expect(out).toContain('550e8400-e29b-41d4-a716-446655440000') }) it('-o name emits ids joined by newline', async () => { const out = await render('name') - expect(out.trim().split('\n').sort()).toEqual(['ws-1', 'ws-2']) + expect(out.trim().split('\n').sort()).toEqual(['550e8400-e29b-41d4-a716-446655440000', '550e8400-e29b-41d4-a716-446655440001']) }) it('empty workspaces (sso scenario) prints external-SSO message regardless of format', async () => { diff --git a/cli/src/commands/set/member/run.test.ts b/cli/src/commands/set/member/run.test.ts index eeba54c1ce..1afac76c6c 100644 --- a/cli/src/commands/set/member/run.test.ts +++ b/cli/src/commands/set/member/run.test.ts @@ -10,8 +10,8 @@ function active(): ActiveContext { email: 'me@example.com', ctx: { account: { id: 'acct-1', email: 'me@example.com', name: 'Me' }, - workspace: { id: 'ws-1', name: 'Default', role: 'owner' }, - available_workspaces: [{ id: 'ws-1', name: 'Default', role: 'owner' }], + workspace: { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }, + available_workspaces: [{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner' }], }, } } @@ -34,11 +34,11 @@ describe('runSetMember', () => { membersFactory: () => client as never, }, ) - expect(client.updateRole).toHaveBeenCalledExactlyOnceWith('ws-1', 'acct-2', { role: 'admin' }) + expect(client.updateRole).toHaveBeenCalledExactlyOnceWith('550e8400-e29b-41d4-a716-446655440000', 'acct-2', { role: 'admin' }) expect(result.data.text()).toMatch(/Set acct-2 role to admin/) expect(result.data.name()).toBe('acct-2') expect(result.data.json()).toEqual({ id: 'acct-2', role: 'admin' }) - expect(result.workspaceId).toBe('ws-1') + expect(result.workspaceId).toBe('550e8400-e29b-41d4-a716-446655440000') }) it('rejects unknown role before any HTTP call', async () => { @@ -75,7 +75,7 @@ describe('runSetMember', () => { it('-w flag overrides resolved workspace', async () => { const client = fakeClient() await runSetMember( - { memberId: 'acct-2', role: 'normal', workspace: 'ws-9' }, + { memberId: 'acct-2', role: 'normal', workspace: '550e8400-e29b-41d4-a716-446655440008' }, { active: active(), http: {} as HttpClient, @@ -83,6 +83,6 @@ describe('runSetMember', () => { membersFactory: () => client as never, }, ) - expect(client.updateRole).toHaveBeenCalledWith('ws-9', 'acct-2', { role: 'normal' }) + expect(client.updateRole).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440008', 'acct-2', { role: 'normal' }) }) }) diff --git a/cli/src/commands/skills/install/registry.test.ts b/cli/src/commands/skills/install/registry.test.ts index 815d15e79a..f4d17848dc 100644 --- a/cli/src/commands/skills/install/registry.test.ts +++ b/cli/src/commands/skills/install/registry.test.ts @@ -41,28 +41,30 @@ describe('detectAgents', () => { }) describe('agent registry paths', () => { + const home = join('home', 'dev') + it('installs Codex skills under ~/.agents/skills, detected via ~/.codex', () => { const codex = AGENTS.find(a => a.name === 'codex') - expect(codex?.probeDir('/home/dev')).toBe('/home/dev/.codex') - expect(codex?.skillDir('/home/dev')).toBe('/home/dev/.agents/skills/difyctl') + expect(codex?.probeDir(home)).toBe(join(home, '.codex')) + expect(codex?.skillDir(home)).toBe(join(home, '.agents', 'skills', 'difyctl')) }) it('installs Claude Code and opencode skills under their native dirs', () => { const claude = AGENTS.find(a => a.name === 'claude-code') const opencode = AGENTS.find(a => a.name === 'opencode') - expect(claude?.skillDir('/home/dev')).toBe('/home/dev/.claude/skills/difyctl') - expect(opencode?.skillDir('/home/dev')).toBe('/home/dev/.config/opencode/skills/difyctl') + expect(claude?.skillDir(home)).toBe(join(home, '.claude', 'skills', 'difyctl')) + expect(opencode?.skillDir(home)).toBe(join(home, '.config', 'opencode', 'skills', 'difyctl')) }) it('installs Cursor under ~/.cursor/skills, detected via ~/.cursor', () => { const cursor = AGENTS.find(a => a.name === 'cursor') - expect(cursor?.probeDir('/home/dev')).toBe('/home/dev/.cursor') - expect(cursor?.skillDir('/home/dev')).toBe('/home/dev/.cursor/skills/difyctl') + expect(cursor?.probeDir(home)).toBe(join(home, '.cursor')) + expect(cursor?.skillDir(home)).toBe(join(home, '.cursor', 'skills', 'difyctl')) }) it('installs pi under ~/.pi/agent/skills, detected via ~/.pi', () => { const pi = AGENTS.find(a => a.name === 'pi') - expect(pi?.probeDir('/home/dev')).toBe('/home/dev/.pi') - expect(pi?.skillDir('/home/dev')).toBe('/home/dev/.pi/agent/skills/difyctl') + expect(pi?.probeDir(home)).toBe(join(home, '.pi')) + expect(pi?.skillDir(home)).toBe(join(home, '.pi', 'agent', 'skills', 'difyctl')) }) }) diff --git a/cli/src/store/store.ts b/cli/src/store/store.ts index 748069ccc0..85b0cd7391 100644 --- a/cli/src/store/store.ts +++ b/cli/src/store/store.ts @@ -32,6 +32,9 @@ abstract class FileBasedStore implements Store { constructor(filePath: string) { this.filePath = filePath this.platform = resolvePlatform() + } + + private ensureDir(): void { fs.mkdirSync(dirname(this.filePath), { recursive: true, mode: DIR_PERM }) } @@ -50,6 +53,7 @@ abstract class FileBasedStore implements Store { } if (this.rawContent !== undefined) { + this.ensureDir() const tmp = `${this.filePath}.tmp.${pid()}.${Date.now()}` try { fs.writeFileSync(tmp, this.rawContent, { mode: FILE_PERM }) @@ -68,6 +72,7 @@ abstract class FileBasedStore implements Store { } lock(): void { + this.ensureDir() try { lockfile.lockSync(`${this.filePath}.lock`, { stale: 30_000, diff --git a/cli/test/e2e/helpers/cleanup-registry.ts b/cli/test/e2e/helpers/cleanup-registry.ts index 9b2d4559ed..1499e9660e 100644 --- a/cli/test/e2e/helpers/cleanup-registry.ts +++ b/cli/test/e2e/helpers/cleanup-registry.ts @@ -52,7 +52,7 @@ export async function cleanupRegisteredConversations(): Promise { if (_conversations.length === 0) return - console.log(`[E2E teardown] Cleaning up ${_conversations.length} staged conversation(s)…`) + console.warn(`[E2E teardown] Cleaning up ${_conversations.length} staged conversation(s)…`) const results = await Promise.allSettled( _conversations.map(({ host, token, appId, conversationId }) => @@ -68,7 +68,7 @@ export async function cleanupRegisteredConversations(): Promise { ) } else { - console.log(`[E2E teardown] All conversations cleaned up.`) + console.warn(`[E2E teardown] All conversations cleaned up.`) } _conversations.length = 0 diff --git a/cli/test/e2e/setup/global-setup.ts b/cli/test/e2e/setup/global-setup.ts index cbfb075fcd..51f961bc37 100644 --- a/cli/test/e2e/setup/global-setup.ts +++ b/cli/test/e2e/setup/global-setup.ts @@ -278,15 +278,15 @@ export async function setup(project: TestProject): Promise { workspaceId: primaryWsId, workspaceName: primaryWsName, ws2Id: secondaryWsId, - chatAppId: provisionedIds.DIFY_E2E_CHAT_APP_ID || E.chatAppId, - workflowAppId: provisionedIds.DIFY_E2E_WORKFLOW_APP_ID || E.workflowAppId, - fileAppId: provisionedIds.DIFY_E2E_FILE_APP_ID || E.fileAppId, - fileChatAppId: provisionedIds.DIFY_E2E_FILE_CHAT_APP_ID || E.fileChatAppId, - hitlAppId: provisionedIds.DIFY_E2E_HITL_APP_ID || E.hitlAppId, - hitlExternalAppId: provisionedIds.DIFY_E2E_HITL_EXTERNAL_APP_ID || E.hitlExternalAppId, + chatAppId: provisionedIds.DIFY_E2E_CHAT_APP_ID || E.chatAppId, + workflowAppId: provisionedIds.DIFY_E2E_WORKFLOW_APP_ID || E.workflowAppId, + fileAppId: provisionedIds.DIFY_E2E_FILE_APP_ID || E.fileAppId, + fileChatAppId: provisionedIds.DIFY_E2E_FILE_CHAT_APP_ID || E.fileChatAppId, + hitlAppId: provisionedIds.DIFY_E2E_HITL_APP_ID || E.hitlAppId, + hitlExternalAppId: provisionedIds.DIFY_E2E_HITL_EXTERNAL_APP_ID || E.hitlExternalAppId, hitlSingleActionAppId: provisionedIds.DIFY_E2E_HITL_SINGLE_ACTION_APP_ID || E.hitlSingleActionAppId, - hitlMultiNodeAppId: provisionedIds.DIFY_E2E_HITL_MULTI_NODE_APP_ID || E.hitlMultiNodeAppId, - ws2AppId: provisionedIds.DIFY_E2E_WS2_APP_ID || E.ws2AppId, + hitlMultiNodeAppId: provisionedIds.DIFY_E2E_HITL_MULTI_NODE_APP_ID || E.hitlMultiNodeAppId, + ws2AppId: provisionedIds.DIFY_E2E_WS2_APP_ID || E.ws2AppId, } // @ts-expect-error — ProvidedContext augmentation cannot be expressed without diff --git a/cli/test/fixtures/dify-mock/scenarios.ts b/cli/test/fixtures/dify-mock/scenarios.ts index 94bfd1b5c7..0fa5d6d33f 100644 --- a/cli/test/fixtures/dify-mock/scenarios.ts +++ b/cli/test/fixtures/dify-mock/scenarios.ts @@ -64,18 +64,18 @@ export const ACCOUNT: AccountFixture = { email: 'tester@dify.ai', name: 'Test Tester', is_external: false, - current_workspace_id: 'ws-1', + current_workspace_id: '550e8400-e29b-41d4-a716-446655440000', } export const WORKSPACES: WorkspaceFixture[] = [ - { id: 'ws-1', name: 'Default', role: 'owner', status: 'normal', is_current: true }, - { id: 'ws-2', name: 'Other', role: 'normal', status: 'normal', is_current: false }, + { id: '550e8400-e29b-41d4-a716-446655440000', name: 'Default', role: 'owner', status: 'normal', is_current: true }, + { id: '550e8400-e29b-41d4-a716-446655440001', name: 'Other', role: 'normal', status: 'normal', is_current: false }, ] export const APPS: AppFixture[] = [ { id: 'app-1', - workspace_id: 'ws-1', + workspace_id: '550e8400-e29b-41d4-a716-446655440000', workspace_name: 'Default', name: 'Greeter', mode: 'chat', @@ -98,7 +98,7 @@ export const APPS: AppFixture[] = [ }, { id: 'app-4', - workspace_id: 'ws-2', + workspace_id: '550e8400-e29b-41d4-a716-446655440001', workspace_name: 'Other', name: 'Researcher', mode: 'agent-chat', @@ -113,7 +113,7 @@ export const APPS: AppFixture[] = [ }, { id: 'app-2', - workspace_id: 'ws-1', + workspace_id: '550e8400-e29b-41d4-a716-446655440000', workspace_name: 'Default', name: 'Workflow', mode: 'workflow', @@ -127,7 +127,7 @@ export const APPS: AppFixture[] = [ }, { id: 'app-3', - workspace_id: 'ws-2', + workspace_id: '550e8400-e29b-41d4-a716-446655440001', workspace_name: 'Other', name: 'OtherWS Bot', mode: 'chat', diff --git a/cli/test/fixtures/dify-mock/server.test.ts b/cli/test/fixtures/dify-mock/server.test.ts index c14762715e..7233f2a23b 100644 --- a/cli/test/fixtures/dify-mock/server.test.ts +++ b/cli/test/fixtures/dify-mock/server.test.ts @@ -59,7 +59,7 @@ describe('dify-mock fixture server', () => { workspaces: Array<{ id: string, name: string, role: string, status: string, current: boolean }> } expect(body.workspaces).toHaveLength(2) - expect(body.workspaces[0]?.id).toBe('ws-1') + expect(body.workspaces[0]?.id).toBe('550e8400-e29b-41d4-a716-446655440000') expect(body.workspaces[0]?.status).toBe('normal') expect(body.workspaces[0]?.current).toBe(true) expect(body.workspaces[1]?.current).toBe(false) @@ -89,11 +89,11 @@ describe('dify-mock fixture server', () => { expect(body.subject_type).toBe('account') expect(body.account?.email).toBe('tester@dify.ai') expect(body.workspaces).toHaveLength(2) - expect(body.default_workspace_id).toBe('ws-1') + expect(body.default_workspace_id).toBe('550e8400-e29b-41d4-a716-446655440000') }) it('GET /openapi/v1/apps respects ?mode filter', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps?workspace_id=ws-1&mode=workflow`, { + const r = await fetch(`${mock.url}/openapi/v1/apps?workspace_id=550e8400-e29b-41d4-a716-446655440000&mode=workflow`, { headers: { Authorization: 'Bearer dfoa_test' }, }) const body = await r.json() as { data: Array<{ mode: string }>, total: number } @@ -103,7 +103,7 @@ describe('dify-mock fixture server', () => { }) it('GET /openapi/v1/apps scopes by workspace_id', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps?workspace_id=ws-2`, { + const r = await fetch(`${mock.url}/openapi/v1/apps?workspace_id=550e8400-e29b-41d4-a716-446655440001`, { headers: { Authorization: 'Bearer dfoa_test' }, }) const body = await r.json() as { data: Array<{ id: string }> } @@ -112,14 +112,14 @@ describe('dify-mock fixture server', () => { }) it('GET /openapi/v1/apps/:id/describe returns 404 for unknown id', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps/nope/describe?workspace_id=ws-1`, { + const r = await fetch(`${mock.url}/openapi/v1/apps/nope/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, { headers: { Authorization: 'Bearer dfoa_test' }, }) expect(r.status).toBe(404) }) it('GET /openapi/v1/apps/:id/describe returns the app for known id', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=ws-1`, { + const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, { headers: { Authorization: 'Bearer dfoa_test' }, }) expect(r.status).toBe(200) @@ -158,7 +158,7 @@ describe('dify-mock fixture server', () => { }) it('GET /openapi/v1/apps/:id/describe?fields=info returns slim payload', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=ws-1&fields=info`, { + const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000&fields=info`, { headers: { Authorization: 'Bearer dfoa_test' }, }) expect(r.status).toBe(200) @@ -169,7 +169,7 @@ describe('dify-mock fixture server', () => { }) it('GET /openapi/v1/apps/:id/describe full returns parameters when present', async () => { - const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=ws-1`, { + const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, { headers: { Authorization: 'Bearer dfoa_test' }, }) expect(r.status).toBe(200) diff --git a/cli/test/fixtures/dify-mock/server.ts b/cli/test/fixtures/dify-mock/server.ts index af04508763..064dad071d 100644 --- a/cli/test/fixtures/dify-mock/server.ts +++ b/cli/test/fixtures/dify-mock/server.ts @@ -178,7 +178,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono { subject_email: ACCOUNT.email, account: { id: ACCOUNT.id, email: ACCOUNT.email, name: ACCOUNT.name }, workspaces: WORKSPACES.map(w => ({ id: w.id, name: w.name, role: w.role })), - default_workspace_id: 'ws-1', + default_workspace_id: ACCOUNT.current_workspace_id, }) }) @@ -227,7 +227,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono { const mode = c.req.query('mode') const tag = c.req.query('tag') const name = c.req.query('name') - const workspaceId = c.req.query('workspace_id') ?? 'ws-1' + const workspaceId = c.req.query('workspace_id') ?? ACCOUNT.current_workspace_id let filtered = APPS.filter(a => a.workspace_id === workspaceId) if (mode !== undefined && mode !== '') filtered = filtered.filter(a => a.mode === mode) @@ -374,7 +374,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono { subject_type: 'account', account: { id: ACCOUNT.id, email: '', name: '' }, workspaces: WORKSPACES.map(w => ({ id: w.id, name: w.name, role: w.role })), - default_workspace_id: 'ws-1', + default_workspace_id: ACCOUNT.current_workspace_id, token_id: 'tok-1', }) } @@ -383,7 +383,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono { subject_type: 'account', account: ACCOUNT, workspaces: WORKSPACES.map(w => ({ id: w.id, name: w.name, role: w.role })), - default_workspace_id: 'ws-1', + default_workspace_id: ACCOUNT.current_workspace_id, token_id: 'tok-1', }) }) diff --git a/cli/vitest.e2e.config.ts b/cli/vitest.e2e.config.ts index c21187b481..7aa0f401ba 100644 --- a/cli/vitest.e2e.config.ts +++ b/cli/vitest.e2e.config.ts @@ -67,31 +67,31 @@ export default defineConfig({ return raw.split(',').map(s => s.trim()).filter(Boolean) return undefined })() - ?? (process.env.DIFY_E2E_MODE === 'local' - ? ['test/e2e/suites/framework/help.e2e.ts', 'test/e2e/suites/agent/**/*.e2e.ts'] - : [ + ?? (process.env.DIFY_E2E_MODE === 'local' + ? ['test/e2e/suites/framework/help.e2e.ts', 'test/e2e/suites/agent/**/*.e2e.ts'] + : [ // auth tests first (most others depend on a valid session) - 'test/e2e/suites/auth/login.e2e.ts', - 'test/e2e/suites/auth/status.e2e.ts', - 'test/e2e/suites/auth/use.e2e.ts', - 'test/e2e/suites/auth/whoami.e2e.ts', - // help (no network, no auth — runs first) - 'test/e2e/suites/framework/help.e2e.ts', - // output format (table / cross-cutting) - 'test/e2e/suites/output/**/*.e2e.ts', - // error handling (cross-cutting error message spec) - 'test/e2e/suites/error-handling/**/*.e2e.ts', - // framework (global flags, non-interactive, debug) - 'test/e2e/suites/framework/**/*.e2e.ts', - // discovery (get app / describe app) - 'test/e2e/suites/discovery/**/*.e2e.ts', - // run tests (require valid token) - 'test/e2e/suites/run/**/*.e2e.ts', - 'test/e2e/suites/agent/**/*.e2e.ts', - // devices + logout LAST — both can revoke tokens - 'test/e2e/suites/auth/devices.e2e.ts', - 'test/e2e/suites/auth/logout.e2e.ts', - ]), + 'test/e2e/suites/auth/login.e2e.ts', + 'test/e2e/suites/auth/status.e2e.ts', + 'test/e2e/suites/auth/use.e2e.ts', + 'test/e2e/suites/auth/whoami.e2e.ts', + // help (no network, no auth — runs first) + 'test/e2e/suites/framework/help.e2e.ts', + // output format (table / cross-cutting) + 'test/e2e/suites/output/**/*.e2e.ts', + // error handling (cross-cutting error message spec) + 'test/e2e/suites/error-handling/**/*.e2e.ts', + // framework (global flags, non-interactive, debug) + 'test/e2e/suites/framework/**/*.e2e.ts', + // discovery (get app / describe app) + 'test/e2e/suites/discovery/**/*.e2e.ts', + // run tests (require valid token) + 'test/e2e/suites/run/**/*.e2e.ts', + 'test/e2e/suites/agent/**/*.e2e.ts', + // devices + logout LAST — both can revoke tokens + 'test/e2e/suites/auth/devices.e2e.ts', + 'test/e2e/suites/auth/logout.e2e.ts', + ]), // E2E calls a real staging server — allow plenty of time per test. testTimeout: 120_000, hookTimeout: 30_000,