mirror of
https://github.com/langgenius/dify.git
synced 2026-06-10 18:24:09 +08:00
fix: run ci properly on pr (#37233)
This commit is contained in:
parent
28cc3fc10d
commit
19d2a4d7a0
7
.github/workflows/cli-tests.yml
vendored
7
.github/workflows/cli-tests.yml
vendored
@ -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' }}
|
||||
|
||||
@ -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')
|
||||
|
||||
|
||||
@ -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' })
|
||||
})
|
||||
})
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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' })
|
||||
})
|
||||
})
|
||||
|
||||
@ -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'))
|
||||
})
|
||||
})
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -52,7 +52,7 @@ export async function cleanupRegisteredConversations(): Promise<void> {
|
||||
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<void> {
|
||||
)
|
||||
}
|
||||
else {
|
||||
console.log(`[E2E teardown] All conversations cleaned up.`)
|
||||
console.warn(`[E2E teardown] All conversations cleaned up.`)
|
||||
}
|
||||
|
||||
_conversations.length = 0
|
||||
|
||||
@ -278,15 +278,15 @@ export async function setup(project: TestProject): Promise<void> {
|
||||
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
|
||||
|
||||
14
cli/test/fixtures/dify-mock/scenarios.ts
vendored
14
cli/test/fixtures/dify-mock/scenarios.ts
vendored
@ -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',
|
||||
|
||||
16
cli/test/fixtures/dify-mock/server.test.ts
vendored
16
cli/test/fixtures/dify-mock/server.test.ts
vendored
@ -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)
|
||||
|
||||
8
cli/test/fixtures/dify-mock/server.ts
vendored
8
cli/test/fixtures/dify-mock/server.ts
vendored
@ -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',
|
||||
})
|
||||
})
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user