Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions WebCA Server API.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,56 @@
{
"name": "Database Users",
"item": [
{
"name": "Get User Info (userinfo)",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{base_url}}/{{hostUid}}/database/users/info/{{dbname}}",
"host": ["{{base_url}}"],
"path": ["{{hostUid}}", "database", "users", "info", "{{dbname}}"]
},
"description": "Get user info (list of users) for a database. CMS task: userinfo. GET /:hostUid/database/users/info/:dbname. Auth: Bearer JWT."
},
"response": []
},
{
"name": "User Verify (userverify)",
"request": {
"method": "POST",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {
"mode": "raw",
"raw": "{\n \"dbname\": \"demodb\",\n \"dbuser\": \"dba\",\n \"dbpasswd\": \"\"\n}"
},
"url": {
"raw": "{{base_url}}/{{hostUid}}/database/users/verify",
"host": ["{{base_url}}"],
"path": ["{{hostUid}}", "database", "users", "verify"]
},
"description": "Verify database user credentials. CMS task: userverify. POST /:hostUid/database/users/verify. Body: dbname, dbuser, dbpasswd. Auth: Bearer JWT."
},
"response": []
},
{
"name": "Create Database User (createuser)",
"request": {
"method": "POST",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {
"mode": "raw",
"raw": "{\n \"dbname\": \"demodb\",\n \"username\": \"yifan\",\n \"userpass\": \"1111\",\n \"groups\": { \"group\": [\"public\"] },\n \"authorization\": []\n}"
},
"url": {
"raw": "{{base_url}}/{{hostUid}}/database/users",
"host": ["{{base_url}}"],
"path": ["{{hostUid}}", "database", "users"]
},
"description": "Create a database user. CMS task: createuser. POST /:hostUid/database/users. Body: dbname, username, userpass, groups, authorization. Auth: Bearer JWT."
},
"response": []
},
{
"name": "Get Database Users",
"request": {
Expand Down Expand Up @@ -1232,6 +1282,20 @@
"description": "Update a database user. Returns empty object on success.\n\n**URI:**\n```\nPUT /:hostUid/database/users/:dbname/:username\n```\n**Example:**\n```\nPUT /host-uid-1/database/users/demodb/yifan\n```\n\n**Authentication:** Required\n**Headers:**\n```\nAuthorization: Bearer <JWT_TOKEN>\nContent-Type: application/json\n```\n\n**Path Parameters:**\n- `hostUid` - Host unique identifier\n- `dbname` - Database name\n- `username` - Username to update\n\n**Request Body:**\n```json\n{\n \"userpass\": \"1111\",\n \"groups\": {\n \"group\": [\"public\"]\n },\n \"authorization\": []\n}\n```\n\n**Success Response (200 OK):**\n```json\n{}\n```\n\n**Error Response (400 Bad Request):**\n```json\n{\n \"statusCode\": 400,\n \"message\": \"Missing required fields: userpass, groups, authorization\",\n \"error\": \"Bad Request\"\n}\n```\n\n**Error Response (404 Not Found):**\n```json\n{\n \"statusCode\": 404,\n \"message\": \"Database or user not found\",\n \"error\": \"Not Found\"\n}\n```"
},
"response": []
},
{
"name": "Delete Database User (deleteuser)",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{base_url}}/{{hostUid}}/database/users/{{dbname}}/{{username}}",
"host": ["{{base_url}}"],
"path": ["{{hostUid}}", "database", "users", "{{dbname}}", "{{username}}"]
},
"description": "Delete a database user. CMS task: deleteuser. DELETE /:hostUid/database/users/:dbname/:username. Auth: Bearer JWT."
},
"response": []
}
],
"description": "Database user management endpoints. All endpoints require JWT authentication."
Expand Down
174 changes: 168 additions & 6 deletions apps/api-server/src/database/user/database-user.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,188 @@ import { DatabaseUserService } from './database-user.service';

describe('DatabaseUserController', () => {
let controller: DatabaseUserController;
let service: DatabaseUserService;
let service: jest.Mocked<DatabaseUserService>;

const mockReq = { user: { sub: 'user-123' } };

beforeEach(async () => {
const mockDatabaseUserService = {
getDatabaseUsers: jest.fn(),
getUserInfo: jest.fn(),
userVerify: jest.fn(),
createUser: jest.fn(),
deleteUser: jest.fn(),
loginDatabase: jest.fn(),
updateUser: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [DatabaseUserController],
providers: [
{
provide: DatabaseUserService,
useValue: {
getDatabaseUsers: jest.fn(),
},
useValue: mockDatabaseUserService,
},
],
}).compile();

controller = module.get<DatabaseUserController>(DatabaseUserController);
service = module.get<DatabaseUserService>(DatabaseUserService);
controller = module.get(DatabaseUserController);
service = module.get(DatabaseUserService);
});

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

describe('getDatabaseUsers', () => {
it('should call service.getDatabaseUsers and return result', async () => {
service.getDatabaseUsers.mockResolvedValue([]);

const result = await controller.getDatabaseUsers(mockReq, 'host-uid-1');

expect(service.getDatabaseUsers).toHaveBeenCalledWith('user-123');
expect(result).toEqual([]);
});
});

describe('getUserInfo', () => {
it('should call service.getUserInfo and return dbname and user list', async () => {
const mockResponse = {
dbname: 'demodb',
user: [{ '@id': '163810704', '@name': 'PUBLIC' }],
};
service.getUserInfo.mockResolvedValue(mockResponse);

const result = await controller.getUserInfo(
mockReq,
'host-uid-1',
'demodb'
);

expect(service.getUserInfo).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
'demodb'
);
expect(result).toEqual(mockResponse);
});
});

describe('userVerify', () => {
it('should call service.userVerify and return { verified: true }', async () => {
const body = { dbname: 'demodb', dbuser: 'dba', dbpasswd: '' };
service.userVerify.mockResolvedValue({ verified: true });

const result = await controller.userVerify(mockReq, 'host-uid-1', body);

expect(service.userVerify).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
'demodb',
'dba',
''
);
expect(result).toEqual({ verified: true });
});
});

describe('createUser', () => {
it('should call service.createUser and return empty object', async () => {
const body = {
dbname: 'demodb',
username: 'yifan',
userpass: '1111',
groups: { group: ['public'] },
authorization: [],
};
service.createUser.mockResolvedValue({});

const result = await controller.createUser(mockReq, 'host-uid-1', body);

expect(service.createUser).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
body.dbname,
body.username,
body.userpass,
body.groups,
body.authorization
);
expect(result).toEqual({});
});
});

describe('deleteUser', () => {
it('should call service.deleteUser and return empty object', async () => {
service.deleteUser.mockResolvedValue({});

const result = await controller.deleteUser(
mockReq,
'host-uid-1',
'demodb',
'yifan'
);

expect(service.deleteUser).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
'demodb',
'yifan'
);
expect(result).toEqual({});
});
});

describe('updateUser', () => {
it('should call service.updateUser and return empty object', async () => {
const body = {
userpass: '1111',
groups: { group: ['public'] },
authorization: [],
};
service.updateUser.mockResolvedValue({});

const result = await controller.updateUser(
mockReq,
'host-uid-1',
'demodb',
'yifan',
body
);

expect(service.updateUser).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
'demodb',
'yifan',
body.userpass,
body.groups,
body.authorization
);
expect(result).toEqual({});
});
});

describe('loginDatabase', () => {
it('should call service.loginDatabase with id and password when body has them', async () => {
const body = { id: 'dba', password: 'pass' };
service.loginDatabase.mockResolvedValue(true);

const result = await controller.loginDatabase(
mockReq,
'host-uid-1',
'demodb',
body
);

expect(service.loginDatabase).toHaveBeenCalledWith(
'user-123',
'host-uid-1',
'demodb',
'dba',
'pass'
);
expect(result).toBe(true);
});
});
});
104 changes: 103 additions & 1 deletion apps/api-server/src/database/user/database-user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { Body, Controller, Get, Logger, Param, Post, Put, Request } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Logger,
Param,
Post,
Put,
Request,
} from '@nestjs/common';
import { DatabaseUserService } from './database-user.service';
import {
CreateDbUserRequest,
DatabaseLoginClientRequest,
UpdateDbUserRequest,
UpdateDbUserResponse,
UserInfoClientResponse,
CreateDbUserResponse,
DeleteDbUserResponse,
UserVerifyRequest,
UserVerifyResponse,
} from '@api-interfaces';
import { ValidationError } from '@error/validation/validation-error';
import { validateRequiredFields } from '@util';
Expand All @@ -23,6 +39,92 @@ export class DatabaseUserController {

constructor(private readonly databaseUserService: DatabaseUserService) {}

/**
* Get user info (list of users) for a database. CMS task: userinfo.
*
* @route GET /:hostUid/database/users/info/:dbname
*/
@Get('info/:dbname')
async getUserInfo(
@Request() req,
@Param('hostUid') hostUid: string,
@Param('dbname') dbname: string
): Promise<UserInfoClientResponse> {
const userId = req.user.sub;
this.logger.log(`Getting user info for database: ${dbname} on host: ${hostUid}`);
return await this.databaseUserService.getUserInfo(userId, hostUid, dbname);
}

/**
* Verify database user credentials. CMS task: userverify.
*
* @route POST /:hostUid/database/users/verify
*/
@Post('verify')
async userVerify(
@Request() req,
@Param('hostUid') hostUid: string,
@Body() body: UserVerifyRequest
): Promise<UserVerifyResponse> {
const userId = req.user.sub;
validateRequiredFields(body, ['dbname', 'dbuser', 'dbpasswd'], 'database/users/verify', this.logger);
this.logger.log(`Verifying user ${body.dbuser} for database: ${body.dbname} on host: ${hostUid}`);
return await this.databaseUserService.userVerify(
userId,
hostUid,
body.dbname,
body.dbuser,
body.dbpasswd
);
}

/**
* Create a database user. CMS task: createuser.
*
* @route POST /:hostUid/database/users
*/
@Post()
async createUser(
@Request() req,
@Param('hostUid') hostUid: string,
@Body() body: CreateDbUserRequest
): Promise<CreateDbUserResponse> {
const userId = req.user.sub;
validateRequiredFields(
body,
['dbname', 'username', 'userpass', 'groups', 'authorization'],
'database/users',
this.logger
);
this.logger.log(`Creating user: ${body.username} in database: ${body.dbname} on host: ${hostUid}`);
return await this.databaseUserService.createUser(
userId,
hostUid,
body.dbname,
body.username,
body.userpass,
body.groups,
body.authorization
);
}

/**
* Delete a database user. CMS task: deleteuser.
*
* @route DELETE /:hostUid/database/users/:dbname/:username
*/
@Delete(':dbname/:username')
async deleteUser(
@Request() req,
@Param('hostUid') hostUid: string,
@Param('dbname') dbname: string,
@Param('username') username: string
): Promise<DeleteDbUserResponse> {
const userId = req.user.sub;
this.logger.log(`Deleting user: ${username} from database: ${dbname} on host: ${hostUid}`);
return await this.databaseUserService.deleteUser(userId, hostUid, dbname, username);
}

/**
* Get list of database users for a specific host.
*
Expand Down
Loading
Loading