mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
test(server): utils (#10028)
This commit is contained in:
@@ -1,80 +1,51 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
|
||||
import { gql } from './common';
|
||||
import { TestingApp } from './testing-app';
|
||||
|
||||
export async function listBlobs(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string
|
||||
): Promise<string[]> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
listBlobs(workspaceId: "${workspaceId}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.listBlobs;
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
listBlobs(workspaceId: "${workspaceId}")
|
||||
}
|
||||
`);
|
||||
return res.listBlobs;
|
||||
}
|
||||
|
||||
export async function getWorkspaceBlobsSize(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string
|
||||
): Promise<number> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
blobsSize
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.workspace.blobsSize;
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
blobsSize
|
||||
}
|
||||
}
|
||||
`);
|
||||
return res.workspace.blobsSize;
|
||||
}
|
||||
|
||||
export async function collectAllBlobSizes(
|
||||
app: INestApplication,
|
||||
token: string
|
||||
): Promise<number> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
currentUser {
|
||||
quotaUsage {
|
||||
storageQuota
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.currentUser.quotaUsage.storageQuota;
|
||||
export async function collectAllBlobSizes(app: TestingApp): Promise<number> {
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
currentUser {
|
||||
quotaUsage {
|
||||
storageQuota
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
return res.currentUser.quotaUsage.storageQuota;
|
||||
}
|
||||
|
||||
export async function setBlob(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
buffer: Buffer
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
const res = await app
|
||||
.POST('/graphql')
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.field(
|
||||
'operations',
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
|
||||
import {
|
||||
DEFAULT_DIMENSIONS,
|
||||
OpenAIProvider,
|
||||
@@ -26,8 +23,8 @@ import {
|
||||
WorkflowNodeType,
|
||||
WorkflowParams,
|
||||
} from '../../plugins/copilot/workflow/types';
|
||||
import { gql } from './common';
|
||||
import { handleGraphQLError, sleep } from './utils';
|
||||
import { TestingApp } from './testing-app';
|
||||
import { sleep } from './utils';
|
||||
|
||||
// @ts-expect-error no error
|
||||
export class MockCopilotTestProvider
|
||||
@@ -159,167 +156,123 @@ export class MockCopilotTestProvider
|
||||
}
|
||||
|
||||
export async function createCopilotSession(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
promptName: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation createCopilotSession($options: CreateChatSessionInput!) {
|
||||
createCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: { options: { workspaceId, docId, promptName } },
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation createCopilotSession($options: CreateChatSessionInput!) {
|
||||
createCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
{ options: { workspaceId, docId, promptName } }
|
||||
);
|
||||
|
||||
handleGraphQLError(res);
|
||||
|
||||
return res.body.data.createCopilotSession;
|
||||
return res.createCopilotSession;
|
||||
}
|
||||
|
||||
export async function updateCopilotSession(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
promptName: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation updateCopilotSession($options: UpdateChatSessionInput!) {
|
||||
updateCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: { options: { sessionId, promptName } },
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation updateCopilotSession($options: UpdateChatSessionInput!) {
|
||||
updateCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
{ options: { sessionId, promptName } }
|
||||
);
|
||||
|
||||
handleGraphQLError(res);
|
||||
|
||||
return res.body.data.updateCopilotSession;
|
||||
return res.updateCopilotSession;
|
||||
}
|
||||
|
||||
export async function forkCopilotSession(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
sessionId: string,
|
||||
latestMessageId: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation forkCopilotSession($options: ForkChatSessionInput!) {
|
||||
forkCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: { workspaceId, docId, sessionId, latestMessageId },
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation forkCopilotSession($options: ForkChatSessionInput!) {
|
||||
forkCopilotSession(options: $options)
|
||||
}
|
||||
`,
|
||||
{ options: { workspaceId, docId, sessionId, latestMessageId } }
|
||||
);
|
||||
|
||||
handleGraphQLError(res);
|
||||
|
||||
return res.body.data.forkCopilotSession;
|
||||
return res.forkCopilotSession;
|
||||
}
|
||||
|
||||
export async function createCopilotMessage(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
content?: string,
|
||||
attachments?: string[],
|
||||
blobs?: ArrayBuffer[],
|
||||
params?: Record<string, string>
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||
createCopilotMessage(options: $options)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
options: { sessionId, content, attachments, blobs, params },
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||
createCopilotMessage(options: $options)
|
||||
}
|
||||
`,
|
||||
{ options: { sessionId, content, attachments, blobs, params } }
|
||||
);
|
||||
|
||||
handleGraphQLError(res);
|
||||
|
||||
return res.body.data.createCopilotMessage;
|
||||
return res.createCopilotMessage;
|
||||
}
|
||||
|
||||
export async function chatWithText(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
messageId?: string,
|
||||
prefix = ''
|
||||
): Promise<string> {
|
||||
const query = messageId ? `?messageId=${messageId}` : '';
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/api/copilot/chat/${sessionId}${prefix}${query}`)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
const res = await app
|
||||
.GET(`/api/copilot/chat/${sessionId}${prefix}${query}`)
|
||||
.expect(200);
|
||||
|
||||
return res.text;
|
||||
}
|
||||
|
||||
export async function chatWithTextStream(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
messageId?: string
|
||||
) {
|
||||
return chatWithText(app, userToken, sessionId, messageId, '/stream');
|
||||
return chatWithText(app, sessionId, messageId, '/stream');
|
||||
}
|
||||
|
||||
export async function chatWithWorkflow(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
messageId?: string
|
||||
) {
|
||||
return chatWithText(app, userToken, sessionId, messageId, '/workflow');
|
||||
return chatWithText(app, sessionId, messageId, '/workflow');
|
||||
}
|
||||
|
||||
export async function chatWithImages(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
sessionId: string,
|
||||
messageId?: string
|
||||
) {
|
||||
return chatWithText(app, userToken, sessionId, messageId, '/images');
|
||||
return chatWithText(app, sessionId, messageId, '/images');
|
||||
}
|
||||
|
||||
export async function unsplashSearch(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
params: Record<string, string> = {}
|
||||
) {
|
||||
const query = new URLSearchParams(params);
|
||||
const res = await request(app.getHttpServer())
|
||||
.get(`/api/copilot/unsplash/photos?${query}`)
|
||||
.auth(userToken, { type: 'bearer' });
|
||||
const res = await app.GET(`/api/copilot/unsplash/photos?${query}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -378,8 +331,7 @@ type History = {
|
||||
};
|
||||
|
||||
export async function getHistories(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
variables: {
|
||||
workspaceId: string;
|
||||
docId?: string;
|
||||
@@ -394,43 +346,36 @@ export async function getHistories(
|
||||
};
|
||||
}
|
||||
): Promise<History[]> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query getCopilotHistories(
|
||||
$workspaceId: String!
|
||||
$docId: String
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
tokens
|
||||
action
|
||||
const res = await app.gql(
|
||||
`
|
||||
query getCopilotHistories(
|
||||
$workspaceId: String!
|
||||
$docId: String
|
||||
$options: QueryChatHistoriesInput
|
||||
) {
|
||||
currentUser {
|
||||
copilot(workspaceId: $workspaceId) {
|
||||
histories(docId: $docId, options: $options) {
|
||||
sessionId
|
||||
tokens
|
||||
action
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
createdAt
|
||||
messages {
|
||||
id
|
||||
role
|
||||
content
|
||||
attachments
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables,
|
||||
})
|
||||
.expect(200);
|
||||
variables
|
||||
);
|
||||
|
||||
handleGraphQLError(res);
|
||||
|
||||
return res.body.data.currentUser?.copilot?.histories || [];
|
||||
return res.currentUser?.copilot?.histories || [];
|
||||
}
|
||||
|
||||
type Prompt = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export * from './blobs';
|
||||
export * from './invite';
|
||||
export * from './permission';
|
||||
export * from './testing-app';
|
||||
export * from './testing-module';
|
||||
export * from './user';
|
||||
export * from './utils';
|
||||
export * from './workspace';
|
||||
|
||||
@@ -1,281 +1,171 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
|
||||
import type { InvitationType } from '../../core/workspaces';
|
||||
import { gql } from './common';
|
||||
|
||||
import type { TestingApp } from './testing-app';
|
||||
export async function inviteUser(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
email: string,
|
||||
sendInviteMail = false
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
invite(workspaceId: "${workspaceId}", email: "${email}", sendInviteMail: ${sendInviteMail})
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.invite;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
invite(workspaceId: "${workspaceId}", email: "${email}", sendInviteMail: ${sendInviteMail})
|
||||
}
|
||||
`);
|
||||
|
||||
return res.invite;
|
||||
}
|
||||
|
||||
export async function inviteUsers(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
emails: string[],
|
||||
sendInviteMail = false
|
||||
): Promise<Array<{ email: string; inviteId?: string; sentSuccess?: boolean }>> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation inviteBatch($workspaceId: String!, $emails: [String!]!, $sendInviteMail: Boolean) {
|
||||
inviteBatch(
|
||||
workspaceId: $workspaceId
|
||||
emails: $emails
|
||||
sendInviteMail: $sendInviteMail
|
||||
) {
|
||||
email
|
||||
inviteId
|
||||
sentSuccess
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { workspaceId, emails, sendInviteMail },
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.inviteBatch;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation inviteBatch($workspaceId: String!, $emails: [String!]!, $sendInviteMail: Boolean) {
|
||||
inviteBatch(
|
||||
workspaceId: $workspaceId
|
||||
emails: $emails
|
||||
sendInviteMail: $sendInviteMail
|
||||
) {
|
||||
email
|
||||
inviteId
|
||||
sentSuccess
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ workspaceId, emails, sendInviteMail }
|
||||
);
|
||||
|
||||
return res.inviteBatch;
|
||||
}
|
||||
|
||||
export async function getInviteLink(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string
|
||||
): Promise<{ link: string; expireTime: string }> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
inviteLink {
|
||||
link
|
||||
expireTime
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.workspace.inviteLink;
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
inviteLink {
|
||||
link
|
||||
expireTime
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return res.workspace.inviteLink;
|
||||
}
|
||||
|
||||
export async function createInviteLink(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
expireTime: 'OneDay' | 'ThreeDays' | 'OneWeek' | 'OneMonth'
|
||||
): Promise<{ link: string; expireTime: string }> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
createInviteLink(workspaceId: "${workspaceId}", expireTime: ${expireTime}) {
|
||||
link
|
||||
expireTime
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.createInviteLink;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
createInviteLink(workspaceId: "${workspaceId}", expireTime: ${expireTime}) {
|
||||
link
|
||||
expireTime
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return res.createInviteLink;
|
||||
}
|
||||
|
||||
export async function revokeInviteLink(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
revokeInviteLink(workspaceId: "${workspaceId}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.revokeInviteLink;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
revokeInviteLink(workspaceId: "${workspaceId}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.revokeInviteLink;
|
||||
}
|
||||
|
||||
export async function acceptInviteById(
|
||||
app: INestApplication,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
inviteId: string,
|
||||
sendAcceptMail = false,
|
||||
token: string = ''
|
||||
sendAcceptMail = false
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
acceptInviteById(workspaceId: "${workspaceId}", inviteId: "${inviteId}", sendAcceptMail: ${sendAcceptMail})
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message, {
|
||||
cause: res.body.errors[0].cause,
|
||||
});
|
||||
}
|
||||
return res.body.data.acceptInviteById;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
acceptInviteById(workspaceId: "${workspaceId}", inviteId: "${inviteId}", sendAcceptMail: ${sendAcceptMail})
|
||||
}
|
||||
`);
|
||||
|
||||
return res.acceptInviteById;
|
||||
}
|
||||
|
||||
export async function approveMember(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
userId: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.auth(token, { type: 'bearer' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
approveMember(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message, {
|
||||
cause: res.body.errors[0].cause,
|
||||
});
|
||||
}
|
||||
return res.body.data.approveMember;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
approveMember(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.approveMember;
|
||||
}
|
||||
|
||||
export async function leaveWorkspace(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
sendLeaveMail = false
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
leaveWorkspace(workspaceId: "${workspaceId}", sendLeaveMail: ${sendLeaveMail})
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data.leaveWorkspace;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
leaveWorkspace(workspaceId: "${workspaceId}", sendLeaveMail: ${sendLeaveMail})
|
||||
}
|
||||
`);
|
||||
|
||||
return res.leaveWorkspace;
|
||||
}
|
||||
|
||||
export async function revokeUser(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
userId: string
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
revoke(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message, {
|
||||
cause: res.body.errors[0].cause,
|
||||
});
|
||||
}
|
||||
return res.body.data.revoke;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
revoke(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.revoke;
|
||||
}
|
||||
|
||||
export async function getInviteInfo(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
inviteId: string
|
||||
): Promise<InvitationType> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
getInviteInfo(inviteId: "${inviteId}") {
|
||||
workspace {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
}
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message, {
|
||||
cause: res.body.errors[0].cause,
|
||||
});
|
||||
}
|
||||
return res.body.data.getInviteInfo;
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
getInviteInfo(inviteId: "${inviteId}") {
|
||||
workspace {
|
||||
id
|
||||
name
|
||||
avatar
|
||||
}
|
||||
user {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return res.getInviteInfo;
|
||||
}
|
||||
|
||||
@@ -1,116 +1,84 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
|
||||
import { DocRole } from '../../core/permission/types';
|
||||
import { gql } from './common';
|
||||
import { TestingApp } from './testing-app';
|
||||
|
||||
export function grantDocUserRoles(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
export async function grantDocUserRoles(
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
userIds: string[],
|
||||
role: DocRole
|
||||
) {
|
||||
return request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
grantDocUserRoles(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
userIds: ["${userIds.join('","')}"],
|
||||
role: ${DocRole[role]}
|
||||
})
|
||||
}
|
||||
`,
|
||||
});
|
||||
return await app.gql(`
|
||||
mutation {
|
||||
grantDocUserRoles(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
userIds: ["${userIds.join('","')}"],
|
||||
role: ${DocRole[role]}
|
||||
})
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
export function revokeDocUserRoles(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
export async function revokeDocUserRoles(
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
userId: string
|
||||
) {
|
||||
return request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
revokeDocUserRoles(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
userId: "${userId}"
|
||||
})
|
||||
}
|
||||
`,
|
||||
});
|
||||
return await app.gql(`
|
||||
mutation {
|
||||
revokeDocUserRoles(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
userId: "${userId}"
|
||||
})
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
export function updateDocDefaultRole(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
export async function updateDocDefaultRole(
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
role: DocRole
|
||||
) {
|
||||
return request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
updateDocDefaultRole(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
role: ${DocRole[role]}
|
||||
})
|
||||
}
|
||||
`,
|
||||
});
|
||||
return await app.gql(`
|
||||
mutation {
|
||||
updateDocDefaultRole(input: {
|
||||
workspaceId: "${workspaceId}",
|
||||
docId: "${docId}",
|
||||
role: ${DocRole[role]}
|
||||
})
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
export async function docGrantedUsersList(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
first = 10,
|
||||
offset = 0
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
doc(docId: "${docId}") {
|
||||
grantedUsersList(pagination: { first: ${first}, offset: ${offset} }) {
|
||||
totalCount
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
role
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
return await app.gql(`
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
doc(docId: "${docId}") {
|
||||
grantedUsersList(pagination: { first: ${first}, offset: ${offset} }) {
|
||||
totalCount
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
role
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
return res.body;
|
||||
}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import {
|
||||
ConsoleLogger,
|
||||
INestApplication,
|
||||
ModuleMetadata,
|
||||
} from '@nestjs/common';
|
||||
import { TestingModuleBuilder } from '@nestjs/testing';
|
||||
import { User } from '@prisma/client';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { ApplyType, GlobalExceptionFilter } from '../../base';
|
||||
import { AuthService } from '../../core/auth';
|
||||
import { UserModel } from '../../models';
|
||||
import { createTestingModule } from './testing-module';
|
||||
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
||||
interface TestingAppMeatdata extends ModuleMetadata {
|
||||
tapModule?(m: TestingModuleBuilder): void;
|
||||
tapApp?(app: INestApplication): void;
|
||||
}
|
||||
|
||||
export type TestUser = Omit<User, 'password'> & { password: string };
|
||||
|
||||
export async function createTestingApp(
|
||||
moduleDef: TestingAppMeatdata = {}
|
||||
): Promise<TestingApp> {
|
||||
const module = await createTestingModule(moduleDef, false);
|
||||
|
||||
const app = module.createNestApplication({
|
||||
cors: true,
|
||||
bodyParser: true,
|
||||
rawBody: true,
|
||||
});
|
||||
const logger = new ConsoleLogger();
|
||||
|
||||
logger.setLogLevels([TEST_LOG_LEVEL]);
|
||||
app.useLogger(logger);
|
||||
|
||||
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
if (moduleDef.tapApp) {
|
||||
moduleDef.tapApp(app);
|
||||
}
|
||||
|
||||
await module.initTestingDB();
|
||||
await app.init();
|
||||
|
||||
return makeTestingApp(app);
|
||||
}
|
||||
|
||||
export function parseCookies(res: supertest.Response) {
|
||||
const cookies = res.get('Set-Cookie') ?? [];
|
||||
const sessionCookie = cookies.reduce(
|
||||
(cookies, cookie) => {
|
||||
const [key, value] = cookie.split(';')[0].split('=');
|
||||
cookies[key] = value;
|
||||
return cookies;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
);
|
||||
|
||||
return sessionCookie;
|
||||
}
|
||||
|
||||
export class TestingApp extends ApplyType<INestApplication>() {
|
||||
private sessionCookie: string | null = null;
|
||||
private currentUserCookie: string | null = null;
|
||||
private readonly userCookies: Set<string> = new Set();
|
||||
|
||||
[Symbol.asyncDispose](): Promise<void> {
|
||||
return this.close();
|
||||
}
|
||||
|
||||
async initTestingDB() {
|
||||
await initTestingDB(this);
|
||||
this.sessionCookie = null;
|
||||
this.currentUserCookie = null;
|
||||
this.userCookies.clear();
|
||||
}
|
||||
|
||||
url() {
|
||||
const server = this.getHttpServer();
|
||||
if (!server.address()) {
|
||||
server.listen();
|
||||
}
|
||||
return `http://localhost:${server.address().port}`;
|
||||
}
|
||||
|
||||
request(
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch',
|
||||
path: string
|
||||
): supertest.Test {
|
||||
return supertest(this.getHttpServer())
|
||||
[method](path)
|
||||
.set('Cookie', [
|
||||
`${AuthService.sessionCookieName}=${this.sessionCookie ?? ''}`,
|
||||
`${AuthService.userCookieName}=${this.currentUserCookie ?? ''}`,
|
||||
]);
|
||||
}
|
||||
|
||||
GET(path: string): supertest.Test {
|
||||
return this.request('get', path);
|
||||
}
|
||||
|
||||
POST(path: string): supertest.Test {
|
||||
return this.request('post', path).on(
|
||||
'response',
|
||||
(res: supertest.Response) => {
|
||||
const cookies = parseCookies(res);
|
||||
|
||||
if (AuthService.sessionCookieName in cookies) {
|
||||
if (this.sessionCookie !== cookies[AuthService.sessionCookieName]) {
|
||||
this.userCookies.clear();
|
||||
}
|
||||
|
||||
this.sessionCookie = cookies[AuthService.sessionCookieName];
|
||||
this.currentUserCookie = cookies[AuthService.userCookieName];
|
||||
if (this.currentUserCookie) {
|
||||
this.userCookies.add(this.currentUserCookie);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
PUT(path: string): supertest.Test {
|
||||
return this.request('put', path);
|
||||
}
|
||||
|
||||
DELETE(path: string): supertest.Test {
|
||||
return this.request('delete', path);
|
||||
}
|
||||
|
||||
PATCH(path: string): supertest.Test {
|
||||
return this.request('patch', path);
|
||||
}
|
||||
|
||||
// TODO(@forehalo): directly make proxy for graphql queries defined in `@affine/graphql`
|
||||
// by calling with `app.apis.createWorkspace({ ...variables })`
|
||||
async gql<Data = any>(query: string, variables?: any): Promise<Data> {
|
||||
const res = await this.POST('/graphql')
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query,
|
||||
variables,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
if (res.body.errors?.length) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
|
||||
return res.body.data;
|
||||
}
|
||||
|
||||
async createUser(email: string, override?: Partial<User>): Promise<TestUser> {
|
||||
const model = this.get(UserModel);
|
||||
// TODO(@forehalo): model factories
|
||||
// TestingData.user.create()
|
||||
const user = await model.create({
|
||||
email,
|
||||
password: '1',
|
||||
name: email,
|
||||
emailVerifiedAt: new Date(),
|
||||
...override,
|
||||
});
|
||||
|
||||
// returned password is not encrypted
|
||||
user.password = '1';
|
||||
|
||||
return user as Omit<User, 'password'> & { password: string };
|
||||
}
|
||||
|
||||
async signup(email: string, override?: Partial<User>) {
|
||||
const user = await this.createUser(email, override);
|
||||
await this.login(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
async login(user: TestUser) {
|
||||
await this.POST('/api/auth/sign-in')
|
||||
.send({
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
.expect(200);
|
||||
}
|
||||
|
||||
async switchUser(userOrId: string | { id: string }) {
|
||||
if (!this.sessionCookie) {
|
||||
throw new Error('No user is logged in.');
|
||||
}
|
||||
|
||||
const userId = typeof userOrId === 'string' ? userOrId : userOrId.id;
|
||||
|
||||
if (userId === this.currentUserCookie) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.userCookies.has(userId)) {
|
||||
this.currentUserCookie = userId;
|
||||
} else {
|
||||
throw new Error(`User [${userId}] is not logged in.`);
|
||||
}
|
||||
}
|
||||
|
||||
async logout(userId?: string) {
|
||||
const res = await this.GET(
|
||||
'/api/auth/sign-out' + (userId ? `?user_id=${userId}` : '')
|
||||
).expect(200);
|
||||
const cookies = parseCookies(res);
|
||||
this.sessionCookie = cookies[AuthService.sessionCookieName];
|
||||
if (!this.sessionCookie) {
|
||||
this.currentUserCookie = null;
|
||||
this.userCookies.clear();
|
||||
} else {
|
||||
this.currentUserCookie = cookies[AuthService.userCookieName];
|
||||
if (userId) {
|
||||
this.userCookies.delete(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeTestingApp(app: INestApplication): TestingApp {
|
||||
const testingApp = new TestingApp();
|
||||
|
||||
return new Proxy(testingApp, {
|
||||
get(target, prop) {
|
||||
// @ts-expect-error override
|
||||
return target[prop] ?? app[prop];
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { ModuleMetadata } from '@nestjs/common';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { Query, Resolver } from '@nestjs/graphql';
|
||||
import {
|
||||
Test,
|
||||
TestingModule as BaseTestingModule,
|
||||
TestingModuleBuilder,
|
||||
} from '@nestjs/testing';
|
||||
|
||||
import { AppModule, FunctionalityModules } from '../../app.module';
|
||||
import { Runtime } from '../../base';
|
||||
import { GqlModule } from '../../base/graphql';
|
||||
import { AuthGuard, AuthModule } from '../../core/auth';
|
||||
import { ModelsModule } from '../../models';
|
||||
import { initTestingDB, TEST_LOG_LEVEL } from './utils';
|
||||
|
||||
interface TestingModuleMeatdata extends ModuleMetadata {
|
||||
tapModule?(m: TestingModuleBuilder): void;
|
||||
}
|
||||
|
||||
export interface TestingModule extends BaseTestingModule {
|
||||
initTestingDB(): Promise<void>;
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
}
|
||||
|
||||
function dedupeModules(modules: NonNullable<ModuleMetadata['imports']>) {
|
||||
const map = new Map();
|
||||
|
||||
modules.forEach(m => {
|
||||
if ('module' in m) {
|
||||
map.set(m.module, m);
|
||||
} else {
|
||||
map.set(m, m);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
@Resolver(() => String)
|
||||
class MockResolver {
|
||||
@Query(() => String)
|
||||
hello() {
|
||||
return 'hello world';
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTestingModule(
|
||||
moduleDef: TestingModuleMeatdata = {},
|
||||
autoInitialize = true
|
||||
): Promise<TestingModule> {
|
||||
// setting up
|
||||
let imports = moduleDef.imports ?? [AppModule];
|
||||
imports =
|
||||
imports[0] === AppModule
|
||||
? [AppModule]
|
||||
: dedupeModules([
|
||||
...FunctionalityModules,
|
||||
ModelsModule,
|
||||
AuthModule,
|
||||
GqlModule,
|
||||
...imports,
|
||||
]);
|
||||
|
||||
const builder = Test.createTestingModule({
|
||||
imports,
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthGuard,
|
||||
},
|
||||
MockResolver,
|
||||
...(moduleDef.providers ?? []),
|
||||
],
|
||||
controllers: moduleDef.controllers,
|
||||
});
|
||||
|
||||
if (moduleDef.tapModule) {
|
||||
moduleDef.tapModule(builder);
|
||||
}
|
||||
|
||||
const module = await builder.compile();
|
||||
|
||||
const testingModule = module as TestingModule;
|
||||
|
||||
testingModule.initTestingDB = async () => {
|
||||
await initTestingDB(module);
|
||||
|
||||
const runtime = module.get(Runtime);
|
||||
// by pass password min length validation
|
||||
await runtime.set('auth/password.min', 1);
|
||||
};
|
||||
|
||||
testingModule[Symbol.asyncDispose] = async () => {
|
||||
await module.close();
|
||||
};
|
||||
|
||||
if (autoInitialize) {
|
||||
// we got a lot smoking tests try to break nestjs
|
||||
// can't tolerate the noisy logs
|
||||
// @ts-expect-error private
|
||||
module.applyLogger({
|
||||
logger: [TEST_LOG_LEVEL],
|
||||
});
|
||||
await testingModule.initTestingDB();
|
||||
await testingModule.init();
|
||||
}
|
||||
|
||||
return testingModule;
|
||||
}
|
||||
@@ -1,201 +1,124 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import request, { type Response } from 'supertest';
|
||||
import { TestingApp } from './testing-app';
|
||||
|
||||
import {
|
||||
AuthService,
|
||||
type ClientTokenType,
|
||||
type CurrentUser,
|
||||
} from '../../core/auth';
|
||||
import { sessionUser } from '../../core/auth/service';
|
||||
import { UserType } from '../../core/user';
|
||||
import { Models } from '../../models';
|
||||
import { gql } from './common';
|
||||
|
||||
export type UserAuthedType = UserType & { token: ClientTokenType };
|
||||
|
||||
export async function internalSignIn(app: INestApplication, userId: string) {
|
||||
const auth = app.get(AuthService);
|
||||
|
||||
const session = await auth.createUserSession(userId);
|
||||
|
||||
return `${AuthService.sessionCookieName}=${session.sessionId}`;
|
||||
}
|
||||
|
||||
export function sessionCookie(headers: any): string {
|
||||
const cookie = headers['set-cookie']?.find((c: string) =>
|
||||
c.startsWith(`${AuthService.sessionCookieName}=`)
|
||||
);
|
||||
|
||||
if (!cookie) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return cookie.split(';')[0];
|
||||
}
|
||||
|
||||
export async function getSession(
|
||||
app: INestApplication,
|
||||
signInRes: Response
|
||||
): Promise<{ user?: CurrentUser }> {
|
||||
const cookie = sessionCookie(signInRes.headers);
|
||||
const res = await request(app.getHttpServer())
|
||||
.get('/api/auth/session')
|
||||
.set('cookie', cookie!)
|
||||
.expect(200);
|
||||
|
||||
return res.body;
|
||||
}
|
||||
|
||||
export async function signUp(
|
||||
app: INestApplication,
|
||||
name: string,
|
||||
email: string,
|
||||
password: string,
|
||||
autoVerifyEmail = true
|
||||
): Promise<UserAuthedType> {
|
||||
const user = await app.get(Models).user.create({
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
emailVerifiedAt: autoVerifyEmail ? new Date() : null,
|
||||
});
|
||||
const { sessionId } = await app.get(AuthService).createUserSession(user.id);
|
||||
|
||||
return {
|
||||
...sessionUser(user),
|
||||
token: { token: sessionId, refresh: '' },
|
||||
};
|
||||
}
|
||||
|
||||
export async function currentUser(app: INestApplication, token: string) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
currentUser {
|
||||
id, name, email, emailVerified, avatarUrl, hasPassword,
|
||||
token { token }
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.currentUser;
|
||||
export async function currentUser(app: TestingApp) {
|
||||
const res = await app.gql(`
|
||||
query {
|
||||
currentUser {
|
||||
id, name, email, emailVerified, avatarUrl, hasPassword,
|
||||
token { token }
|
||||
}
|
||||
}
|
||||
`);
|
||||
return res.currentUser;
|
||||
}
|
||||
|
||||
export async function sendChangeEmail(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
email: string,
|
||||
callbackUrl: string
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
sendChangeEmail(email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
sendChangeEmail(email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.body.data.sendChangeEmail;
|
||||
return res.sendChangeEmail;
|
||||
}
|
||||
|
||||
export async function sendSetPasswordEmail(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
email: string,
|
||||
callbackUrl: string
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
sendSetPasswordEmail(email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
sendSetPasswordEmail(email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.body.data.sendChangeEmail;
|
||||
return res.sendSetPasswordEmail;
|
||||
}
|
||||
|
||||
export async function changePassword(
|
||||
app: INestApplication,
|
||||
app: TestingApp,
|
||||
userId: string,
|
||||
token: string,
|
||||
password: string
|
||||
): Promise<string> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation changePassword($token: String!, $userId: String!, $password: String!) {
|
||||
changePassword(token: $token, userId: $userId, newPassword: $password)
|
||||
}
|
||||
`,
|
||||
variables: { token, password, userId },
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.changePassword;
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
changePassword(token: "${token}", userId: "${userId}", newPassword: "${password}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.changePassword;
|
||||
}
|
||||
|
||||
export async function sendVerifyChangeEmail(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
token: string,
|
||||
email: string,
|
||||
callbackUrl: string
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
sendVerifyChangeEmail(token:"${token}", email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
sendVerifyChangeEmail(token: "${token}", email: "${email}", callbackUrl: "${callbackUrl}")
|
||||
}
|
||||
`);
|
||||
|
||||
return res.body.data.sendVerifyChangeEmail;
|
||||
return res.sendVerifyChangeEmail;
|
||||
}
|
||||
|
||||
export async function changeEmail(
|
||||
app: INestApplication,
|
||||
userToken: string,
|
||||
app: TestingApp,
|
||||
token: string,
|
||||
email: string
|
||||
): Promise<UserAuthedType> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(userToken, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
changeEmail(token: "${token}", email: "${email}") {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
email
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.changeEmail;
|
||||
) {
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
changeEmail(token: "${token}", email: "${email}") {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
email
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return res.changeEmail;
|
||||
}
|
||||
|
||||
export async function deleteAccount(app: TestingApp) {
|
||||
const res = await app.gql(`
|
||||
mutation {
|
||||
deleteAccount {
|
||||
success
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
return res.deleteAccount.success;
|
||||
}
|
||||
|
||||
export async function updateAvatar(app: TestingApp, avatar: Buffer) {
|
||||
return app
|
||||
.POST('/graphql')
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
name: 'uploadAvatar',
|
||||
query: `mutation uploadAvatar($avatar: Upload!) {
|
||||
uploadAvatar(avatar: $avatar) {
|
||||
avatarUrl
|
||||
}
|
||||
}`,
|
||||
variables: { avatar: null },
|
||||
})
|
||||
)
|
||||
.field('map', JSON.stringify({ '0': ['variables.avatar'] }))
|
||||
.attach('0', avatar, {
|
||||
filename: 'test.png',
|
||||
contentType: 'image/png',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,25 +1,10 @@
|
||||
import { INestApplication, LogLevel, ModuleMetadata } from '@nestjs/common';
|
||||
import { APP_GUARD, ModuleRef } from '@nestjs/core';
|
||||
import { Query, Resolver } from '@nestjs/graphql';
|
||||
import {
|
||||
Test,
|
||||
TestingModule as BaseTestingModule,
|
||||
TestingModuleBuilder,
|
||||
} from '@nestjs/testing';
|
||||
import { INestApplicationContext, LogLevel } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
||||
import type { Response } from 'supertest';
|
||||
import supertest from 'supertest';
|
||||
|
||||
import { AppModule, FunctionalityModules } from '../../app.module';
|
||||
import { AFFiNELogger, GlobalExceptionFilter, Runtime } from '../../base';
|
||||
import { GqlModule } from '../../base/graphql';
|
||||
import { AuthGuard, AuthModule } from '../../core/auth';
|
||||
import { RefreshFeatures0001 } from '../../data/migrations/0001-refresh-features';
|
||||
import { ModelsModule } from '../../models';
|
||||
|
||||
const TEST_LOG_LEVEL: LogLevel =
|
||||
export const TEST_LOG_LEVEL: LogLevel =
|
||||
(process.env.TEST_LOG_LEVEL as LogLevel) ?? 'fatal';
|
||||
|
||||
async function flushDB(client: PrismaClient) {
|
||||
@@ -38,190 +23,10 @@ async function flushDB(client: PrismaClient) {
|
||||
);
|
||||
}
|
||||
|
||||
interface TestingModuleMetadata extends ModuleMetadata {
|
||||
tapModule?(m: TestingModuleBuilder): void;
|
||||
tapApp?(app: INestApplication): void;
|
||||
}
|
||||
|
||||
const initTestingDB = async (ref: ModuleRef) => {
|
||||
const db = ref.get(PrismaClient, { strict: false });
|
||||
export async function initTestingDB(context: INestApplicationContext) {
|
||||
const db = context.get(PrismaClient, { strict: false });
|
||||
await flushDB(db);
|
||||
await RefreshFeatures0001.up(db, ref);
|
||||
};
|
||||
|
||||
export type TestingModule = BaseTestingModule & {
|
||||
initTestingDB(): Promise<void>;
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
};
|
||||
|
||||
export type TestingApp = INestApplication & {
|
||||
initTestingDB(): Promise<void>;
|
||||
[Symbol.asyncDispose](): Promise<void>;
|
||||
// get the url of the http server, e.g. http://localhost:random-port
|
||||
getHttpServerUrl(): string;
|
||||
};
|
||||
|
||||
function dedupeModules(modules: NonNullable<ModuleMetadata['imports']>) {
|
||||
const map = new Map();
|
||||
|
||||
modules.forEach(m => {
|
||||
if ('module' in m) {
|
||||
map.set(m.module, m);
|
||||
} else {
|
||||
map.set(m, m);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(map.values());
|
||||
}
|
||||
|
||||
@Resolver(() => String)
|
||||
class MockResolver {
|
||||
@Query(() => String)
|
||||
hello() {
|
||||
return 'hello world';
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTestingModule(
|
||||
moduleDef: TestingModuleMetadata = {},
|
||||
autoInitialize = true
|
||||
): Promise<TestingModule> {
|
||||
// setting up
|
||||
let imports = moduleDef.imports ?? [];
|
||||
imports =
|
||||
imports[0] === AppModule
|
||||
? [AppModule]
|
||||
: dedupeModules([
|
||||
...FunctionalityModules,
|
||||
ModelsModule,
|
||||
AuthModule,
|
||||
GqlModule,
|
||||
...imports,
|
||||
]);
|
||||
|
||||
const builder = Test.createTestingModule({
|
||||
imports,
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
useClass: AuthGuard,
|
||||
},
|
||||
MockResolver,
|
||||
...(moduleDef.providers ?? []),
|
||||
],
|
||||
controllers: moduleDef.controllers,
|
||||
});
|
||||
|
||||
if (moduleDef.tapModule) {
|
||||
moduleDef.tapModule(builder);
|
||||
}
|
||||
|
||||
const m = await builder.compile();
|
||||
|
||||
const testingModule = m as TestingModule;
|
||||
testingModule.initTestingDB = async () => {
|
||||
await initTestingDB(m.get(ModuleRef));
|
||||
// we got a lot smoking tests try to break nestjs
|
||||
// can't tolerate the noisy logs
|
||||
// @ts-expect-error private
|
||||
m.applyLogger({
|
||||
logger: [TEST_LOG_LEVEL],
|
||||
});
|
||||
const runtime = m.get(Runtime);
|
||||
// by pass password min length validation
|
||||
await runtime.set('auth/password.min', 1);
|
||||
};
|
||||
testingModule[Symbol.asyncDispose] = async () => {
|
||||
await m.close();
|
||||
};
|
||||
|
||||
if (autoInitialize) {
|
||||
await testingModule.initTestingDB();
|
||||
await testingModule.init();
|
||||
}
|
||||
|
||||
return testingModule;
|
||||
}
|
||||
|
||||
export async function createTestingApp(
|
||||
moduleDef: TestingModuleMetadata = {}
|
||||
): Promise<{ module: TestingModule; app: TestingApp }> {
|
||||
const m = await createTestingModule(moduleDef, false);
|
||||
|
||||
const app = m.createNestApplication({
|
||||
cors: true,
|
||||
bodyParser: true,
|
||||
rawBody: true,
|
||||
}) as TestingApp;
|
||||
const logger = new AFFiNELogger();
|
||||
|
||||
logger.setLogLevels([TEST_LOG_LEVEL]);
|
||||
app.useLogger(logger);
|
||||
|
||||
app.useGlobalFilters(new GlobalExceptionFilter(app.getHttpAdapter()));
|
||||
app.use(
|
||||
graphqlUploadExpress({
|
||||
maxFileSize: 10 * 1024 * 1024,
|
||||
maxFiles: 5,
|
||||
})
|
||||
);
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
if (moduleDef.tapApp) {
|
||||
moduleDef.tapApp(app);
|
||||
}
|
||||
|
||||
await m.initTestingDB();
|
||||
await app.init();
|
||||
|
||||
app.initTestingDB = m.initTestingDB.bind(m);
|
||||
app[Symbol.asyncDispose] = async () => {
|
||||
await m[Symbol.asyncDispose]();
|
||||
await app.close();
|
||||
};
|
||||
|
||||
app.getHttpServerUrl = () => {
|
||||
const server = app.getHttpServer();
|
||||
if (!server.address()) {
|
||||
server.listen();
|
||||
}
|
||||
return `http://localhost:${server.address().port}`;
|
||||
};
|
||||
|
||||
return {
|
||||
module: m,
|
||||
app: app,
|
||||
};
|
||||
}
|
||||
|
||||
export function handleGraphQLError(resp: Response) {
|
||||
const { errors } = resp.body;
|
||||
if (errors) {
|
||||
const cause = errors[0];
|
||||
const stacktrace = cause.extensions?.stacktrace;
|
||||
throw new Error(
|
||||
stacktrace
|
||||
? Array.isArray(stacktrace)
|
||||
? stacktrace.join('\n')
|
||||
: String(stacktrace)
|
||||
: cause.message,
|
||||
cause
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function gql(app: INestApplication, query?: string) {
|
||||
const req = supertest(app.getHttpServer())
|
||||
.post('/graphql')
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' });
|
||||
|
||||
if (query) {
|
||||
return req.send({ query });
|
||||
}
|
||||
|
||||
return req;
|
||||
await RefreshFeatures0001.up(db, context.get(ModuleRef));
|
||||
}
|
||||
|
||||
export async function sleep(ms: number) {
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import type { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
|
||||
import { WorkspaceRole } from '../../core/permission/types';
|
||||
import type { WorkspaceType } from '../../core/workspaces';
|
||||
import { gql } from './common';
|
||||
import { TestingApp } from './testing-app';
|
||||
|
||||
export async function createWorkspace(
|
||||
app: INestApplication,
|
||||
token: string
|
||||
): Promise<WorkspaceType> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
export async function createWorkspace(app: TestingApp): Promise<WorkspaceType> {
|
||||
const res = await app
|
||||
.POST('/graphql')
|
||||
.set({
|
||||
'x-request-id': 'test',
|
||||
'x-operation-name': 'test',
|
||||
})
|
||||
.field(
|
||||
'operations',
|
||||
JSON.stringify({
|
||||
@@ -26,181 +22,141 @@ export async function createWorkspace(
|
||||
})
|
||||
)
|
||||
.field('map', JSON.stringify({ '0': ['variables.init'] }))
|
||||
.attach('0', Buffer.from([0, 0]), 'init.data')
|
||||
.expect(200);
|
||||
.attach('0', Buffer.from([0, 0]), 'init.data');
|
||||
|
||||
return res.body.data.createWorkspace;
|
||||
}
|
||||
|
||||
export async function getWorkspacePublicPages(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
export async function getWorkspacePublicDocs(
|
||||
app: TestingApp,
|
||||
workspaceId: string
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
publicPages {
|
||||
id
|
||||
mode
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.workspace.publicPages;
|
||||
const res = await app.gql(
|
||||
`
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
publicDocs {
|
||||
id
|
||||
mode
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.workspace.publicDocs;
|
||||
}
|
||||
|
||||
export async function getWorkspace(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
skip = 0,
|
||||
take = 8
|
||||
): Promise<WorkspaceType> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
id, members(skip: ${skip}, take: ${take}) { id, name, email, permission, inviteId, status }
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.workspace;
|
||||
const res = await app.gql(
|
||||
`
|
||||
query {
|
||||
workspace(id: "${workspaceId}") {
|
||||
id,
|
||||
members(skip: ${skip}, take: ${take}) { id, name, email, permission, inviteId, status }
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.workspace;
|
||||
}
|
||||
|
||||
export async function updateWorkspace(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
isPublic: boolean
|
||||
): Promise<boolean> {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
updateWorkspace(input: { id: "${workspaceId}", public: ${isPublic} }) {
|
||||
public
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.data.updateWorkspace.public;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation {
|
||||
updateWorkspace(input: { id: "${workspaceId}", public: ${isPublic} }) {
|
||||
public
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.updateWorkspace.public;
|
||||
}
|
||||
|
||||
export async function publishDoc(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
publishDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
|
||||
id
|
||||
mode
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.errors?.[0]?.message || res.body.data?.publishDoc;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation {
|
||||
publishDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
|
||||
id
|
||||
mode
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.publishDoc;
|
||||
}
|
||||
|
||||
export async function revokePublicDoc(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
docId: string
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
revokePublicDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
|
||||
id
|
||||
mode
|
||||
public
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
return res.body.errors?.[0]?.message || res.body.data?.revokePublicDoc;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation {
|
||||
revokePublicDoc(workspaceId: "${workspaceId}", docId: "${docId}") {
|
||||
id
|
||||
mode
|
||||
public
|
||||
}
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.revokePublicDoc;
|
||||
}
|
||||
|
||||
export async function grantMember(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
userId: string,
|
||||
permission: WorkspaceRole
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
grantMember(
|
||||
workspaceId: "${workspaceId}"
|
||||
userId: "${userId}"
|
||||
permission: ${WorkspaceRole[permission]}
|
||||
)
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data?.grantMember;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation {
|
||||
grantMember(
|
||||
workspaceId: "${workspaceId}"
|
||||
userId: "${userId}"
|
||||
permission: ${WorkspaceRole[permission]}
|
||||
)
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.grantMember;
|
||||
}
|
||||
|
||||
export async function revokeMember(
|
||||
app: INestApplication,
|
||||
token: string,
|
||||
app: TestingApp,
|
||||
workspaceId: string,
|
||||
userId: string
|
||||
) {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post(gql)
|
||||
.auth(token, { type: 'bearer' })
|
||||
.set({ 'x-request-id': 'test', 'x-operation-name': 'test' })
|
||||
.send({
|
||||
query: `
|
||||
mutation {
|
||||
revoke(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`,
|
||||
})
|
||||
.expect(200);
|
||||
if (res.body.errors) {
|
||||
throw new Error(res.body.errors[0].message);
|
||||
}
|
||||
return res.body.data?.revokeMember;
|
||||
const res = await app.gql(
|
||||
`
|
||||
mutation {
|
||||
revoke(workspaceId: "${workspaceId}", userId: "${userId}")
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
return res.revoke;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user