Skip to content

Commit

Permalink
Merge branch 'didi:develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
xixiIBN5100 authored May 31, 2024
2 parents bc45081 + 404ba36 commit 1e42e53
Show file tree
Hide file tree
Showing 110 changed files with 6,489 additions and 910 deletions.
8 changes: 8 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SurveyResponseModule } from './modules/surveyResponse/surveyResponse.mo
import { AuthModule } from './modules/auth/auth.module';
import { MessageModule } from './modules/message/message.module';
import { FileModule } from './modules/file/file.module';
import { WorkspaceModule } from './modules/workspace/workspace.module';

import { join } from 'path';

Expand All @@ -31,6 +32,9 @@ import { ClientEncrypt } from './models/clientEncrypt.entity';
import { Word } from './models/word.entity';
import { MessagePushingTask } from './models/messagePushingTask.entity';
import { MessagePushingLog } from './models/messagePushingLog.entity';
import { WorkspaceMember } from './models/workspaceMember.entity';
import { Workspace } from './models/workspace.entity';
import { Collaborator } from './models/collaborator.entity';

import { LoggerProvider } from './logger/logger.provider';
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
Expand Down Expand Up @@ -74,6 +78,9 @@ import { Logger } from './logger';
Word,
MessagePushingTask,
MessagePushingLog,
Workspace,
WorkspaceMember,
Collaborator,
],
};
},
Expand All @@ -92,6 +99,7 @@ import { Logger } from './logger';
}),
MessageModule,
FileModule,
WorkspaceModule,
],
controllers: [AppController],
providers: [
Expand Down
4 changes: 3 additions & 1 deletion server/src/enums/exceptionCode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export enum EXCEPTION_CODE {
AUTHENTICATION_FAILED = 1001, // 没有权限
AUTHENTICATION_FAILED = 1001, // 未授权
PARAMETER_ERROR = 1002, // 参数有误
NO_PERMISSION = 1003, // 没有操作权限

USER_EXISTS = 2001, // 用户已存在
USER_NOT_EXISTS = 2002, // 用户不存在
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
Expand Down
20 changes: 20 additions & 0 deletions server/src/enums/surveyPermission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export enum SURVEY_PERMISSION {
SURVEY_CONF_MANAGE = 'SURVEY_CONF_MANAGE',
SURVEY_RESPONSE_MANAGE = 'SURVEY_RESPONSE_MANAGE',
SURVEY_COOPERATION_MANAGE = 'SURVEY_COOPERATION_MANAGE',
}

export const SURVEY_PERMISSION_DESCRIPTION = {
SURVEY_CONF_MANAGE: {
name: '问卷配置管理',
value: SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
},
surveyResponseManage: {
name: '问卷分析管理',
value: SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
},
surveyCooperatorManage: {
name: '协作者管理',
value: SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
},
};
41 changes: 41 additions & 0 deletions server/src/enums/workspace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export enum ROLE {
ADMIN = 'admin',
USER = 'user',
}

export const ROLE_DESCRIPTION = {
ADMIN: {
name: '管理员',
value: ROLE.ADMIN,
},
USER: {
name: '用户',
value: ROLE.USER,
},
};

export enum PERMISSION {
READ_WORKSPACE = 'READ_WORKSPACE',
WRITE_WORKSPACE = 'WRITE_WORKSPACE',
READ_MEMBER = 'READ_MEMBER',
WRITE_MEMBER = 'WRITE_MEMBER',
READ_SURVEY = 'READ_SURVEY',
WRITE_SURVEY = 'WRITE_SURVEY',
}

export const ROLE_PERMISSION: Record<ROLE, PERMISSION[]> = {
[ROLE.ADMIN]: [
PERMISSION.READ_WORKSPACE,
PERMISSION.WRITE_WORKSPACE,
PERMISSION.READ_MEMBER,
PERMISSION.WRITE_MEMBER,
PERMISSION.READ_SURVEY,
PERMISSION.WRITE_SURVEY,
],
[ROLE.USER]: [
PERMISSION.READ_WORKSPACE,
PERMISSION.READ_MEMBER,
PERMISSION.READ_SURVEY,
PERMISSION.WRITE_SURVEY,
],
};
55 changes: 55 additions & 0 deletions server/src/exceptions/__test/httpExceptions.filter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpExceptionsFilter } from '../httpExceptions.filter';
import { ArgumentsHost } from '@nestjs/common';
import { HttpException } from '../httpException';
import { Response } from 'express';

describe('HttpExceptionsFilter', () => {
let filter: HttpExceptionsFilter;
let mockArgumentsHost: ArgumentsHost;
let mockResponse: Partial<Response>;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [HttpExceptionsFilter],
}).compile();

filter = module.get<HttpExceptionsFilter>(HttpExceptionsFilter);

mockResponse = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};

mockArgumentsHost = {
switchToHttp: jest.fn().mockReturnThis(),
getResponse: jest.fn().mockReturnValue(mockResponse),
} as unknown as ArgumentsHost;
});

it('should return 500 status and "Internal Server Error" message for generic errors', () => {
const genericError = new Error('Some error');

filter.catch(genericError, mockArgumentsHost);

expect(mockResponse.status).toHaveBeenCalledWith(500);
expect(mockResponse.json).toHaveBeenCalledWith({
message: 'Internal Server Error',
code: 500,
errmsg: 'Some error',
});
});

it('should return 200 status and specific message for HttpException', () => {
const httpException = new HttpException('Specific error message', 1001);

filter.catch(httpException, mockArgumentsHost);

expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.json).toHaveBeenCalledWith({
message: 'Specific error message',
code: 1001,
errmsg: 'Specific error message',
});
});
});
1 change: 0 additions & 1 deletion server/src/exceptions/httpExceptions.filter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// all-exceptions.filter.ts
import {
ExceptionFilter,
Catch,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { HttpException } from './httpException';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';

export class NoSurveyPermissionException extends HttpException {
export class NoPermissionException extends HttpException {
constructor(public readonly message: string) {
super(message, EXCEPTION_CODE.NO_SURVEY_PERMISSION);
super(message, EXCEPTION_CODE.NO_PERMISSION);
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { Authtication } from './authtication';
import { Authentication } from '../authentication.guard';
import { AuthService } from 'src/modules/auth/services/auth.service';
import { AuthenticationException } from 'src/exceptions/authException';
import { User } from 'src/models/user.entity';

jest.mock('jsonwebtoken');

describe('Authtication', () => {
let guard: Authtication;
describe('Authentication', () => {
let guard: Authentication;
let authService: AuthService;
let configService: ConfigService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
Authtication,
Authentication,
{
provide: AuthService,
useValue: {
Expand All @@ -31,7 +31,7 @@ describe('Authtication', () => {
],
}).compile();

guard = module.get<Authtication>(Authtication);
guard = module.get<Authentication>(Authentication);
authService = module.get<AuthService>(AuthService);
configService = module.get<ConfigService>(ConfigService);
});
Expand Down
139 changes: 139 additions & 0 deletions server/src/guards/__test/survey.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { Reflector } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext } from '@nestjs/common';

import { SurveyGuard } from '../survey.guard';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { NoPermissionException } from 'src/exceptions/noPermissionException';
import { SurveyMeta } from 'src/models/surveyMeta.entity';
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
import { Collaborator } from 'src/models/collaborator.entity';

describe('SurveyGuard', () => {
let guard: SurveyGuard;
let reflector: Reflector;
let collaboratorService: CollaboratorService;
let surveyMetaService: SurveyMetaService;
let workspaceMemberService: WorkspaceMemberService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SurveyGuard,
{
provide: Reflector,
useValue: {
get: jest.fn(),
},
},
{
provide: CollaboratorService,
useValue: {
getCollaborator: jest.fn(),
},
},
{
provide: SurveyMetaService,
useValue: {
getSurveyById: jest.fn(),
},
},
{
provide: WorkspaceMemberService,
useValue: {
findOne: jest.fn(),
},
},
],
}).compile();

guard = module.get<SurveyGuard>(SurveyGuard);
reflector = module.get<Reflector>(Reflector);
collaboratorService = module.get<CollaboratorService>(CollaboratorService);
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
workspaceMemberService = module.get<WorkspaceMemberService>(
WorkspaceMemberService,
);
});

it('should be defined', () => {
expect(guard).toBeDefined();
});

it('should allow access if no surveyId is present', async () => {
const context = createMockExecutionContext();
jest.spyOn(reflector, 'get').mockReturnValue(null);

const result = await guard.canActivate(context);
expect(result).toBe(true);
});

it('should throw SurveyNotFoundException if survey does not exist', async () => {
const context = createMockExecutionContext();
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
jest.spyOn(surveyMetaService, 'getSurveyById').mockResolvedValue(null);

await expect(guard.canActivate(context)).rejects.toThrow(
SurveyNotFoundException,
);
});

it('should allow access if user is the owner of the survey', async () => {
const context = createMockExecutionContext();
const surveyMeta = { owner: 'testUser', workspaceId: null };
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
jest
.spyOn(surveyMetaService, 'getSurveyById')
.mockResolvedValue(surveyMeta as SurveyMeta);

const result = await guard.canActivate(context);
expect(result).toBe(true);
});

it('should allow access if user is a workspace member', async () => {
const context = createMockExecutionContext();
const surveyMeta = { owner: 'anotherUser', workspaceId: 'workspaceId' };
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
jest
.spyOn(surveyMetaService, 'getSurveyById')
.mockResolvedValue(surveyMeta as SurveyMeta);
jest
.spyOn(workspaceMemberService, 'findOne')
.mockResolvedValue({} as WorkspaceMember);

const result = await guard.canActivate(context);
expect(result).toBe(true);
});

it('should throw NoPermissionException if user has no permissions', async () => {
const context = createMockExecutionContext();
const surveyMeta = { owner: 'anotherUser', workspaceId: null };
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.surveyId');
jest.spyOn(reflector, 'get').mockReturnValueOnce(['requiredPermission']);
jest
.spyOn(surveyMetaService, 'getSurveyById')
.mockResolvedValue(surveyMeta as SurveyMeta);
jest
.spyOn(collaboratorService, 'getCollaborator')
.mockResolvedValue({ permissions: [] } as Collaborator);

await expect(guard.canActivate(context)).rejects.toThrow(
NoPermissionException,
);
});

function createMockExecutionContext(): ExecutionContext {
return {
switchToHttp: jest.fn().mockReturnValue({
getRequest: jest.fn().mockReturnValue({
user: { username: 'testUser', _id: 'testUserId' },
params: { surveyId: 'surveyId' },
}),
}),
getHandler: jest.fn(),
} as unknown as ExecutionContext;
}
});
Loading

0 comments on commit 1e42e53

Please sign in to comment.