Skip to content

Commit 7484f8a

Browse files
committed
refactor: add http get, update and delete endpoints
- Covering get single, update, and delete operations - Small corrections on create and update operations - Adding Postman API tests - Updating swagger
1 parent bba7508 commit 7484f8a

11 files changed

Lines changed: 4279 additions & 324 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2025
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
import { createSpyObj } from '../../../test/helper/jest.js'
7+
import { deleteProxyProfile } from './delete.js'
8+
import { jest } from '@jest/globals'
9+
import { type SpyInstance, spyOn } from 'jest-mock'
10+
11+
describe('Proxy - Delete', () => {
12+
let resSpy
13+
let req
14+
let deleteSpy: SpyInstance<any>
15+
let checkSpy: SpyInstance<any>
16+
17+
beforeEach(() => {
18+
resSpy = createSpyObj('Response', [
19+
'status',
20+
'json',
21+
'end',
22+
'send'
23+
])
24+
req = {
25+
db: { proxyConfigs: { delete: jest.fn(), checkProfileExits: jest.fn() } },
26+
query: {},
27+
params: { proxyName: 'proxyConfigName' },
28+
tenantId: '',
29+
method: 'DELETE'
30+
}
31+
deleteSpy = spyOn(req.db.proxyConfigs, 'delete').mockResolvedValue({})
32+
checkSpy = spyOn(req.db.proxyConfigs, 'checkProfileExits').mockResolvedValue(true)
33+
34+
resSpy.status.mockReturnThis()
35+
resSpy.json.mockReturnThis()
36+
resSpy.send.mockReturnThis()
37+
})
38+
it('should delete', async () => {
39+
checkSpy = spyOn(req.db.proxyConfigs, 'checkProfileExits').mockResolvedValue(true)
40+
await deleteProxyProfile(req, resSpy)
41+
expect(resSpy.status).toHaveBeenCalledWith(204)
42+
})
43+
it('should handle not found', async () => {
44+
checkSpy = spyOn(req.db.proxyConfigs, 'checkProfileExits').mockResolvedValue(false)
45+
await deleteProxyProfile(req, resSpy)
46+
expect(resSpy.status).toHaveBeenCalledWith(404)
47+
})
48+
it('should handle error', async () => {
49+
checkSpy = spyOn(req.db.proxyConfigs, 'checkProfileExits').mockResolvedValue(true)
50+
spyOn(req.db.proxyConfigs, 'delete').mockRejectedValue(null)
51+
await deleteProxyProfile(req, resSpy)
52+
expect(deleteSpy).toHaveBeenCalledWith('proxyConfigName', req.tenantId)
53+
expect(resSpy.status).toHaveBeenCalledWith(500)
54+
})
55+
})

src/routes/admin/proxy/delete.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2022
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
import Logger from '../../../Logger.js'
7+
import { MqttProvider } from '../../../utils/MqttProvider.js'
8+
import { type Request, type Response } from 'express'
9+
import handleError from '../../../utils/handleError.js'
10+
import { RPSError } from '../../../utils/RPSError.js'
11+
import { API_UNEXPECTED_EXCEPTION, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js'
12+
13+
export async function deleteProxyProfile(req: Request, res: Response): Promise<void> {
14+
const { proxyName } = req.params
15+
const tenantId = req.tenantId || ''
16+
const log = new Logger('deleteProxyProfile')
17+
try {
18+
const proxyConfigExists: boolean = await req.db.proxyConfigs.checkProfileExits(proxyName, tenantId)
19+
if (!proxyConfigExists) {
20+
throw new RPSError(NOT_FOUND_MESSAGE('Proxy', proxyName), NOT_FOUND_EXCEPTION)
21+
} else {
22+
const results: boolean | null = await req.db.proxyConfigs.delete(proxyName, tenantId)
23+
if (results) {
24+
log.verbose(`Deleted proxy profile : ${proxyName}`)
25+
MqttProvider.publishEvent('success', ['deleteProxyProfile'], `Deleted proxy configuration : ${proxyName}`)
26+
res.status(204).json(results).end()
27+
} else {
28+
throw new RPSError(API_UNEXPECTED_EXCEPTION('Error deleting proxy configuration'))
29+
}
30+
}
31+
} catch (error) {
32+
handleError(log, 'deleteProxyProfile', req, res, error)
33+
}
34+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2025
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
import { createSpyObj } from '../../../test/helper/jest.js'
7+
import { jest } from '@jest/globals'
8+
import { type SpyInstance, spyOn } from 'jest-mock'
9+
import { editProxyProfile } from './edit.js'
10+
11+
describe('Proxy - Edit', () => {
12+
let resSpy
13+
let req
14+
let updateSpy: SpyInstance<any>
15+
let getSpy: SpyInstance<any>
16+
17+
beforeEach(() => {
18+
resSpy = createSpyObj('Response', [
19+
'status',
20+
'json',
21+
'end',
22+
'send'
23+
])
24+
req = {
25+
db: { proxyConfigs: { update: jest.fn(), getByName: jest.fn() } },
26+
query: {},
27+
params: { proxyConfigAddress: 'proxyConfigAddress' },
28+
tenantId: '',
29+
method: 'PATCH',
30+
body: {
31+
address: 'intel.com',
32+
infoFormat: 201,
33+
networkDnsSuffix: 'vprodemo',
34+
port: 443,
35+
tenantId: 'foo'
36+
}
37+
}
38+
updateSpy = spyOn(req.db.proxyConfigs, 'update').mockResolvedValue({})
39+
getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue({})
40+
41+
resSpy.status.mockReturnThis()
42+
resSpy.json.mockReturnThis()
43+
resSpy.send.mockReturnThis()
44+
})
45+
it('should update', async () => {
46+
getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue({})
47+
await editProxyProfile(req, resSpy)
48+
expect(getSpy).toHaveBeenCalledWith(req.body.proxyName, req.tenantId)
49+
expect(resSpy.status).toHaveBeenCalledWith(200)
50+
})
51+
it('should handle not found', async () => {
52+
getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue(null)
53+
await editProxyProfile(req, resSpy)
54+
expect(getSpy).toHaveBeenCalledWith(req.body.proxyName, req.tenantId)
55+
expect(resSpy.status).toHaveBeenCalledWith(404)
56+
})
57+
it('should handle error', async () => {
58+
getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue({})
59+
spyOn(req.db.proxyConfigs, 'update').mockRejectedValue(null)
60+
await editProxyProfile(req, resSpy)
61+
expect(getSpy).toHaveBeenCalledWith(req.body.proxyName, req.tenantId)
62+
expect(resSpy.status).toHaveBeenCalledWith(500)
63+
})
64+
})

src/routes/admin/proxy/edit.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2022
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
import Logger from '../../../Logger.js'
7+
import { MqttProvider } from '../../../utils/MqttProvider.js'
8+
import { type Request, type Response } from 'express'
9+
import handleError from '../../../utils/handleError.js'
10+
import { RPSError } from '../../../utils/RPSError.js'
11+
import { API_UNEXPECTED_EXCEPTION, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js'
12+
import { ProxyConfig } from 'models/RCS.Config.js'
13+
14+
export async function editProxyProfile(req: Request, res: Response): Promise<void> {
15+
const newProxy: ProxyConfig = req.body
16+
newProxy.tenantId = req.tenantId || ''
17+
const log = new Logger('editProxyProfile')
18+
try {
19+
const oldProxy: ProxyConfig | null = await req.db.proxyConfigs.getByName(newProxy.proxyName, req.tenantId)
20+
21+
if (oldProxy == null) {
22+
throw new RPSError(NOT_FOUND_MESSAGE('Proxy', newProxy.proxyName), NOT_FOUND_EXCEPTION)
23+
} else {
24+
const proxiConfig: ProxyConfig = await getUpdatedData(newProxy, oldProxy)
25+
const results = await req.db.proxyConfigs.update(proxiConfig)
26+
if (results) {
27+
MqttProvider.publishEvent('success', ['editProxyConfig'], `Updated proxy configuration : ${newProxy.proxyName}`)
28+
res.status(200).json(results).end()
29+
} else {
30+
throw new RPSError(API_UNEXPECTED_EXCEPTION('Error updating proxy configuration'))
31+
}
32+
}
33+
} catch (error) {
34+
handleError(log, 'proxyConfigAccessInfo', req, res, error)
35+
}
36+
}
37+
38+
export const getUpdatedData = async (newProxy: ProxyConfig, oldProxy: ProxyConfig): Promise<ProxyConfig> => {
39+
const proxyConfig: ProxyConfig = { proxyName: newProxy.proxyName } as ProxyConfig
40+
proxyConfig.address = newProxy.address ?? oldProxy.address
41+
proxyConfig.infoFormat = newProxy.infoFormat ?? oldProxy.infoFormat
42+
proxyConfig.networkDnsSuffix = newProxy.networkDnsSuffix ?? oldProxy.networkDnsSuffix
43+
proxyConfig.port = newProxy.port ?? oldProxy.port
44+
proxyConfig.tenantId = oldProxy.tenantId
45+
return proxyConfig
46+
}

src/routes/admin/proxy/get.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*********************************************************************
2+
* Copyright (c) Intel Corporation 2025
3+
* SPDX-License-Identifier: Apache-2.0
4+
**********************************************************************/
5+
6+
import { createSpyObj } from '../../../test/helper/jest.js'
7+
import { jest } from '@jest/globals'
8+
import { type SpyInstance, spyOn } from 'jest-mock'
9+
import { getProxyProfile } from './get.js'
10+
import { ProxyConfig } from 'models/RCS.Config.js'
11+
12+
describe('Proxy - Get', () => {
13+
let resSpy
14+
let req
15+
let proxyConfig: ProxyConfig
16+
let getSpy: SpyInstance<any>
17+
18+
beforeEach(() => {
19+
resSpy = createSpyObj('Response', [
20+
'status',
21+
'json',
22+
'end',
23+
'send'
24+
])
25+
26+
req = {
27+
db: { proxyConfigs: { getByName: jest.fn() } },
28+
query: {},
29+
params: { proxyName: 'proxyConfigName' },
30+
tenantId: '',
31+
method: 'GET'
32+
}
33+
34+
getSpy = spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue(
35+
(proxyConfig = {
36+
proxyName: 'proxyConfigName',
37+
address: 'intel.com',
38+
infoFormat: 201,
39+
networkDnsSuffix: 'vprodemo',
40+
port: 443,
41+
tenantId: 'foo'
42+
})
43+
)
44+
45+
resSpy.status.mockReturnThis()
46+
resSpy.json.mockReturnThis()
47+
resSpy.send.mockReturnThis()
48+
})
49+
it('should get', async () => {
50+
await getProxyProfile(req, resSpy)
51+
expect(getSpy).toHaveBeenCalledWith('proxyConfigName', req.tenantId)
52+
expect(resSpy.status).toHaveBeenCalledWith(200)
53+
})
54+
it('should handle not found', async () => {
55+
spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue(null)
56+
await getProxyProfile(req, resSpy)
57+
expect(resSpy.status).toHaveBeenCalledWith(404)
58+
})
59+
it('should handle error', async () => {
60+
spyOn(req.db.proxyConfigs, 'getByName').mockRejectedValue(null)
61+
await getProxyProfile(req, resSpy)
62+
expect(getSpy).toHaveBeenCalledWith('proxyConfigName', req.tenantId)
63+
expect(resSpy.status).toHaveBeenCalledWith(500)
64+
})
65+
})

src/routes/admin/proxy/get.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Logger from '../../../Logger.js'
2+
import { MqttProvider } from '../../../utils/MqttProvider.js'
3+
import { type Request, type Response } from 'express'
4+
import handleError from '../../../utils/handleError.js'
5+
import { RPSError } from '../../../utils/RPSError.js'
6+
import { API_RESPONSE, NOT_FOUND_EXCEPTION, NOT_FOUND_MESSAGE } from '../../../utils/constants.js'
7+
import { ProxyConfig } from 'models/RCS.Config.js'
8+
9+
export async function getProxyProfile(req: Request, res: Response) {
10+
const { proxyName } = req.params
11+
const tenantId = req.tenantId || ''
12+
const log = new Logger('getProxyProfile')
13+
try {
14+
const result: ProxyConfig | null = await req.db.proxyConfigs.getByName(proxyName, tenantId)
15+
if (result == null) {
16+
throw new RPSError(NOT_FOUND_MESSAGE('Proxy', proxyName), NOT_FOUND_EXCEPTION)
17+
} else {
18+
MqttProvider.publishEvent('success', ['getProxyProfile'], `Sent Profile : ${proxyName}`)
19+
res.status(200).json(API_RESPONSE(result)).end()
20+
}
21+
} catch (error) {
22+
handleError(log, 'getProxyProfile', req, res, error)
23+
}
24+
}

src/routes/admin/proxy/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ import validateMiddleware from '../../../middleware/validate.js'
88
import { odataValidator } from '../odataValidator.js'
99
import { allProxyProfiles } from './all.js'
1010
import { createProxyProfile } from './create.js'
11-
import { proxyValidator } from './proxyValidator.js'
11+
import { proxyUpdateValidator, proxyValidator } from './proxyValidator.js'
12+
import { deleteProxyProfile } from './delete.js'
13+
import { getProxyProfile } from './get.js'
14+
import { editProxyProfile } from './edit.js'
1215

13-
const profileRouter: Router = Router()
16+
const proxyRouter: Router = Router()
1417

15-
profileRouter.get('/', odataValidator(), validateMiddleware, allProxyProfiles)
16-
profileRouter.post('/', proxyValidator(), validateMiddleware, createProxyProfile)
17-
export default profileRouter
18+
proxyRouter.get('/', odataValidator(), validateMiddleware, allProxyProfiles)
19+
proxyRouter.get('/:proxyName', getProxyProfile)
20+
proxyRouter.post('/', proxyValidator(), validateMiddleware, createProxyProfile)
21+
proxyRouter.patch('/', proxyUpdateValidator(), validateMiddleware, editProxyProfile)
22+
proxyRouter.delete('/:proxyName', deleteProxyProfile)
23+
export default proxyRouter

src/routes/admin/proxy/proxyValidator.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,46 @@ export const proxyValidator = (): any => [
5656
.isLength({ max: 192 })
5757
.withMessage('Domain name of the network maximum length is 192')
5858
]
59+
60+
export const proxyUpdateValidator = (): any => [
61+
// Validate and normalize infoFormat first so conditional checks can rely on a number
62+
check('infoFormat')
63+
.not()
64+
.isEmpty()
65+
.withMessage('Server address format is required')
66+
.isInt()
67+
.toInt()
68+
.isIn([
69+
3,
70+
4,
71+
201
72+
])
73+
.withMessage('Server address format should be either 3(IPV4), 4(IPV6) or 201(FQDN)'),
74+
75+
// address presence
76+
check('address').not().isEmpty().withMessage('Server address is required'),
77+
78+
// address format based on infoFormat
79+
check('address')
80+
.if((_, { req }) => req.body.infoFormat === 3)
81+
.isIP(4)
82+
.withMessage('infoFormat 3 requires IPV4 server address'),
83+
check('address')
84+
.if((_, { req }) => req.body.infoFormat === 4)
85+
.isIP(6)
86+
.withMessage('infoFormat 4 requires IPV6 server address'),
87+
check('address')
88+
.if((_, { req }) => req.body.infoFormat === 201)
89+
.isFQDN({ require_tld: true, allow_underscores: false, allow_numeric_tld: false })
90+
.withMessage('infoFormat 201 requires FQDN server address'),
91+
92+
check('port').exists().isPort().withMessage('Port value should range between 1 and 65535'),
93+
check('networkDnsSuffix')
94+
.not()
95+
.isEmpty()
96+
.withMessage('Domain name of the network is required')
97+
.isFQDN({ require_tld: true, allow_underscores: false })
98+
.withMessage('Domain name of the network should contain alphanumeric and hyphens in the middle')
99+
.isLength({ max: 192 })
100+
.withMessage('Domain name of the network maximum length is 192')
101+
]

0 commit comments

Comments
 (0)