Skip to content

Commit ae292ef

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
1 parent f327fcf commit ae292ef

8 files changed

Lines changed: 341 additions & 5 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: { proxyConfigAccessInfo: 'proxyConfigAccessInfo' },
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('proxyConfigAccessInfo', 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 { proxyConfigAccessInfo } = req.params
15+
const tenantId = req.tenantId || ''
16+
const log = new Logger('deleteProxyProfile')
17+
try {
18+
const proxyConfigExists: boolean = await req.db.proxyConfigs.checkProfileExits(proxyConfigAccessInfo, tenantId)
19+
if (!proxyConfigExists) {
20+
throw new RPSError(NOT_FOUND_MESSAGE('AMT', proxyConfigAccessInfo), NOT_FOUND_EXCEPTION)
21+
} else {
22+
const results: boolean | null = await req.db.proxyConfigs.delete(proxyConfigAccessInfo, tenantId)
23+
if (results) {
24+
log.verbose(`Deleted proxy profile : ${proxyConfigAccessInfo}`)
25+
MqttProvider.publishEvent('success', ['deleteProxyProfiles'], `Deleted proxy configuration : ${proxyConfigAccessInfo}`)
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, 'proxyConfigAccessInfo', 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: { proxyConfigAccessInfo: 'proxyConfigAccessInfo' },
28+
tenantId: '',
29+
method: 'PATCH',
30+
body: {
31+
accessInfo : "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.accessInfo, 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.accessInfo, 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.accessInfo, req.tenantId)
62+
expect(resSpy.status).toHaveBeenCalledWith(500)
63+
})
64+
})

src/routes/admin/proxy/edit.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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('deleteProxyProfile')
18+
try {
19+
const oldProxy: ProxyConfig | null = await req.db.proxyConfigs.getByName(newProxy.accessInfo, req.tenantId)
20+
21+
if (oldProxy == null) {
22+
throw new RPSError(NOT_FOUND_MESSAGE('AMT', newProxy.accessInfo), 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.accessInfo}`)
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 = { accessInfo: newProxy.accessInfo } as ProxyConfig
40+
proxyConfig.infoFormat = newProxy.infoFormat ?? oldProxy.infoFormat
41+
proxyConfig.networkDnsSuffix = newProxy.networkDnsSuffix ?? oldProxy.networkDnsSuffix
42+
proxyConfig.port = newProxy.port ?? oldProxy.port
43+
proxyConfig.tenantId = oldProxy.tenantId
44+
return proxyConfig
45+
}

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

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 { deleteProxyProfile } from './delete.js'
8+
import { jest } from '@jest/globals'
9+
import { type SpyInstance, spyOn } from 'jest-mock'
10+
import { getProxyProfile } from './get.js'
11+
import { ProxyConfig } from 'models/RCS.Config.js'
12+
13+
14+
describe('Proxy - Delete', () => {
15+
let resSpy
16+
let req
17+
let proxyConfig: ProxyConfig
18+
let getSpy: SpyInstance<any>
19+
20+
beforeEach(() => {
21+
resSpy = createSpyObj('Response', [
22+
'status',
23+
'json',
24+
'end',
25+
'send'
26+
])
27+
28+
req = {
29+
db: { proxyConfigs: { getByName: jest.fn() } },
30+
query: {},
31+
params: { proxyConfigAccessInfo: 'proxyConfigAccessInfo' },
32+
tenantId: '',
33+
method: 'GET'
34+
}
35+
36+
getSpy = spyOn(req.db.proxyConfigs, 'getByName',).mockResolvedValue(proxyConfig = {
37+
accessInfo : "intel.com",
38+
infoFormat : 201,
39+
networkDnsSuffix : "vprodemo",
40+
port : 443,
41+
tenantId: "foo"
42+
})
43+
44+
resSpy.status.mockReturnThis()
45+
resSpy.json.mockReturnThis()
46+
resSpy.send.mockReturnThis()
47+
})
48+
it('should get', async () => {
49+
await getProxyProfile(req, resSpy)
50+
expect(getSpy).toHaveBeenCalledWith('proxyConfigAccessInfo', req.tenantId)
51+
expect(resSpy.status).toHaveBeenCalledWith(200)
52+
})
53+
it('should handle not found', async () => {
54+
spyOn(req.db.proxyConfigs, 'getByName').mockResolvedValue(null)
55+
await getProxyProfile(req, resSpy)
56+
expect(resSpy.status).toHaveBeenCalledWith(404)
57+
})
58+
it('should handle error', async () => {
59+
spyOn(req.db.proxyConfigs, 'getByName').mockRejectedValue(null)
60+
await getProxyProfile(req, resSpy)
61+
expect(getSpy).toHaveBeenCalledWith('proxyConfigAccessInfo', req.tenantId)
62+
expect(resSpy.status).toHaveBeenCalledWith(500)
63+
})
64+
})

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 { proxyConfigAccessInfo } = 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(proxyConfigAccessInfo, tenantId)
15+
if (result == null) {
16+
throw new RPSError(NOT_FOUND_MESSAGE('AMT', proxyConfigAccessInfo), NOT_FOUND_EXCEPTION)
17+
} else {
18+
MqttProvider.publishEvent('success', ['getProxyProfile'], `Sent Profile : ${proxyConfigAccessInfo}`)
19+
res.status(200).json(API_RESPONSE(result)).end()
20+
}
21+
} catch (error) {
22+
handleError(log, 'proxyConfigAccessInfo', 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('/:accessInfo', getProxyProfile)
20+
proxyRouter.post('/', proxyValidator(), validateMiddleware, createProxyProfile)
21+
proxyRouter.patch('/', proxyUpdateValidator(), validateMiddleware, editProxyProfile)
22+
proxyRouter.delete('/:accessInfo', deleteProxyProfile)
23+
export default proxyRouter

src/routes/admin/proxy/proxyValidator.ts

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

0 commit comments

Comments
 (0)