mirror of
https://github.com/langgenius/dify.git
synced 2026-06-14 12:51:07 +08:00
216 lines
6.8 KiB
TypeScript
216 lines
6.8 KiB
TypeScript
import type { StubServer } from '@test/fixtures/stub-server'
|
|
import { testHttpClient } from '@test/fixtures/http-client'
|
|
import { jsonResponder, startStubServer } from '@test/fixtures/stub-server'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { isHttpClientError } from '@/errors/base'
|
|
import { MembersClient } from './members.js'
|
|
import { WorkspacesClient } from './workspaces.js'
|
|
|
|
function makeClient(host: string): MembersClient {
|
|
return new MembersClient(testHttpClient(host, 'dfoa_test'))
|
|
}
|
|
|
|
describe('MembersClient.list', () => {
|
|
let stub: StubServer
|
|
|
|
afterEach(async () => {
|
|
await stub?.stop()
|
|
})
|
|
|
|
it('GETs /workspaces/<id>/members and returns parsed envelope', async () => {
|
|
stub = await startStubServer(cap =>
|
|
jsonResponder(
|
|
200,
|
|
{
|
|
page: 1,
|
|
limit: 20,
|
|
total: 1,
|
|
has_more: false,
|
|
data: [
|
|
{ id: 'm-1', name: 'Mia', email: 'mia@e.com', role: 'admin', status: 'active' },
|
|
],
|
|
},
|
|
cap,
|
|
))
|
|
|
|
const result = await makeClient(stub.url).list('ws-1')
|
|
|
|
expect(stub.captured.method).toBe('GET')
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members')
|
|
expect(result.data[0]?.email).toBe('mia@e.com')
|
|
})
|
|
|
|
it('URL-encodes workspace id', async () => {
|
|
stub = await startStubServer(cap =>
|
|
jsonResponder(200, { page: 1, limit: 20, total: 0, has_more: false, data: [] }, cap))
|
|
|
|
await makeClient(stub.url).list('ws with space')
|
|
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws%20with%20space/members')
|
|
})
|
|
|
|
it('forwards page/limit as query params', async () => {
|
|
stub = await startStubServer(cap =>
|
|
jsonResponder(200, { page: 2, limit: 50, total: 0, has_more: false, data: [] }, cap))
|
|
|
|
await makeClient(stub.url).list('ws-1', { page: 2, limit: 50 })
|
|
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members?page=2&limit=50')
|
|
})
|
|
|
|
it('propagates server 403 as classified BaseError', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(403, { error: 'forbidden' }, cap))
|
|
|
|
await expect(makeClient(stub.url).list('ws-1')).rejects.toSatisfy(
|
|
err => isHttpClientError(err) && err.httpStatus === 403,
|
|
)
|
|
})
|
|
|
|
it('propagates 404 as classified BaseError', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(404, { error: 'not found' }, cap))
|
|
|
|
await expect(makeClient(stub.url).list('ws-missing')).rejects.toSatisfy(
|
|
err => isHttpClientError(err) && err.httpStatus === 404,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('MembersClient.invite', () => {
|
|
let stub: StubServer
|
|
|
|
afterEach(async () => {
|
|
await stub?.stop()
|
|
})
|
|
|
|
it('POSTs JSON body and returns parsed invite response', async () => {
|
|
stub = await startStubServer(cap =>
|
|
jsonResponder(
|
|
201,
|
|
{
|
|
result: 'success',
|
|
email: 'new@e.com',
|
|
role: 'normal',
|
|
member_id: 'acct-9',
|
|
invite_url: 'https://console.example.com/activate?email=new&token=tok',
|
|
tenant_id: 'ws-1',
|
|
},
|
|
cap,
|
|
))
|
|
|
|
const result = await makeClient(stub.url).invite('ws-1', {
|
|
email: 'new@e.com',
|
|
role: 'normal',
|
|
})
|
|
|
|
expect(stub.captured.method).toBe('POST')
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members')
|
|
expect(JSON.parse(stub.captured.body ?? '{}')).toEqual({
|
|
email: 'new@e.com',
|
|
role: 'normal',
|
|
})
|
|
expect(result.member_id).toBe('acct-9')
|
|
expect(result.invite_url).toContain('token=tok')
|
|
})
|
|
|
|
it('propagates 400 (already in tenant) as classified BaseError', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(400, { error: 'already in tenant' }, cap))
|
|
|
|
await expect(
|
|
makeClient(stub.url).invite('ws-1', { email: 'u@e.com', role: 'normal' }),
|
|
).rejects.toSatisfy(err => isHttpClientError(err) && err.httpStatus === 400)
|
|
})
|
|
})
|
|
|
|
describe('MembersClient.remove', () => {
|
|
let stub: StubServer
|
|
|
|
afterEach(async () => {
|
|
await stub?.stop()
|
|
})
|
|
|
|
it('DELETEs member by id and returns success', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(200, { result: 'success' }, cap))
|
|
|
|
const result = await makeClient(stub.url).remove('ws-1', 'm-1')
|
|
|
|
expect(stub.captured.method).toBe('DELETE')
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members/m-1')
|
|
expect(result.result).toBe('success')
|
|
})
|
|
|
|
it('propagates 400 (cannot operate self / cannot remove owner)', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(400, { error: 'cannot operate self' }, cap))
|
|
|
|
await expect(makeClient(stub.url).remove('ws-1', 'm-1')).rejects.toSatisfy(
|
|
err => isHttpClientError(err) && err.httpStatus === 400,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('MembersClient.updateRole', () => {
|
|
let stub: StubServer
|
|
|
|
afterEach(async () => {
|
|
await stub?.stop()
|
|
})
|
|
|
|
it('PUTs role payload to /role subresource', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(200, { result: 'success' }, cap))
|
|
|
|
const result = await makeClient(stub.url).updateRole('ws-1', 'm-1', { role: 'admin' })
|
|
|
|
expect(stub.captured.method).toBe('PUT')
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members/m-1/role')
|
|
expect(JSON.parse(stub.captured.body ?? '{}')).toEqual({ role: 'admin' })
|
|
expect(result.result).toBe('success')
|
|
})
|
|
|
|
it('propagates 400 (admin cannot demote owner)', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(400, { error: 'no permission' }, cap))
|
|
|
|
await expect(
|
|
makeClient(stub.url).updateRole('ws-1', 'm-1', { role: 'admin' }),
|
|
).rejects.toSatisfy(err => isHttpClientError(err) && err.httpStatus === 400)
|
|
})
|
|
})
|
|
|
|
describe('WorkspacesClient.switch (integration with stub)', () => {
|
|
let stub: StubServer
|
|
|
|
afterEach(async () => {
|
|
await stub?.stop()
|
|
})
|
|
|
|
it('POSTs /workspaces/<id>/switch and returns workspace detail', async () => {
|
|
stub = await startStubServer(cap =>
|
|
jsonResponder(
|
|
200,
|
|
{
|
|
id: 'ws-1',
|
|
name: 'Workspace 1',
|
|
role: 'owner',
|
|
status: 'normal',
|
|
current: true,
|
|
created_at: '2026-05-18T00:00:00Z',
|
|
},
|
|
cap,
|
|
))
|
|
|
|
const client = new WorkspacesClient(testHttpClient(stub.url, 'dfoa_test'))
|
|
const result = await client.switch('ws-1')
|
|
|
|
expect(stub.captured.method).toBe('POST')
|
|
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/switch')
|
|
expect(result.current).toBe(true)
|
|
})
|
|
|
|
it('propagates 404 (non-member)', async () => {
|
|
stub = await startStubServer(cap => jsonResponder(404, { error: 'not found' }, cap))
|
|
|
|
const client = new WorkspacesClient(testHttpClient(stub.url, 'dfoa_test'))
|
|
await expect(client.switch('ws-x')).rejects.toSatisfy(
|
|
err => isHttpClientError(err) && err.httpStatus === 404,
|
|
)
|
|
})
|
|
})
|