Skip to content

Commit dd05622

Browse files
authored
Create system proxy endpoint (#2229)
https://eaflood.atlassian.net/browse/WATER-3787 DEFRA/water-abstraction-team#54 Going forward, we intend all new functionality to be built in the [water-abstraction-system](https://github.com/DEFRA/water-abstraction-system) repo. We even intend to _try_ and migrate any existing functionality we touch. In a perfect world, we'll end up with all the existing repos becoming defunct and a clean, performant service all in one repo. To get there though, we still need to work with the existing services and the existing environments. In those, the only apps exposed are the internal and eternal UI which are based on this repo. So, for any requests we want the water-abstraction-system to handle we need to 'proxy' them via the water-abstraction-ui. So, this change adds the ability for the UI to proxy all requests sent to `/system` to the water-abstraction-system app behind the scenes. One of the challenges we hit was assets. We realised when we requested a view, assets would also be requested. So, included in the change is the ability for the UI to proxy asset requests for the system as well.
1 parent 18fcf89 commit dd05622

File tree

8 files changed

+383
-3
lines changed

8 files changed

+383
-3
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ CRM_URI=
1313
IDM_URI=
1414
PERMIT_URI=
1515
RETURNS_URI=
16+
SYSTEM_URI=
1617

1718
# Remote log capturing details
1819
ERRBIT_KEY=

src/internal/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ module.exports = {
110110
crm_v2: crmUri.replace('1.0', '2.0'),
111111
idm: process.env.IDM_URI || 'http://127.0.0.1:8003/idm/1.0',
112112
permits: process.env.PERMIT_URI || 'http://127.0.0.1:8004/API/1.0/',
113-
returns: process.env.RETURNS_URI || 'http://127.0.0.1:8006/returns/1.0'
113+
returns: process.env.RETURNS_URI || 'http://127.0.0.1:8006/returns/1.0',
114+
system: process.env.SYSTEM_URI || 'http://127.0.0.1:8013'
114115
},
115116

116117
testMode,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const ServiceClient = require('shared/lib/connectors/services/ServiceClient')
2+
3+
class SystemProxyService extends ServiceClient {
4+
async getToPath (path) {
5+
// joinUrl appends the given path to the service url, hence we still need to call it here
6+
const url = this.joinUrl(path)
7+
const result = await this.serviceRequest.get(url)
8+
return result
9+
}
10+
}
11+
12+
module.exports = SystemProxyService

src/internal/modules/routes.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const config = require('../config')
44

5-
// External only routes
5+
// Internal only routes
66
const acceptanceTestsProxyRoutes = require('./acceptance-tests-proxy/routes')
77
const coreRoutes = require('./core/routes')
88
const contentRoutes = require('./content/routes')
@@ -28,6 +28,7 @@ const viewLicences = require('./view-licences/routes')
2828
const gaugingStations = require('./gauging-stations/routes')
2929
const customers = require('./customers/routes')
3030
const notes = require('./notes/routes')
31+
const systemProxyRoutes = require('./system-proxy/routes')
3132

3233
// Shared routes
3334
const healthRoutes = require('../../shared/modules/health/routes')
@@ -60,7 +61,8 @@ const routes = [
6061
...Object.values(gaugingStations),
6162
...Object.values(customers),
6263
...Object.values(notes),
63-
...Object.values(healthRoutes)
64+
...Object.values(healthRoutes),
65+
...systemProxyRoutes
6466
]
6567

6668
if (config.featureToggles.acceptanceTestsProxy) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict'
2+
3+
const SystemProxyService = require('../../lib/connectors/services/water/SystemProxyService')
4+
5+
const config = require('../../config')
6+
const { logger } = require('../../logger')
7+
8+
/**
9+
* Forwards GET requests to the water-abstraction-system
10+
*
11+
* When the app runs in AWS you can't access the water-abstraction-service because port 8001 is not exposed. So, this
12+
* endpoint and controller in the app allows us to still run acceptance tests by providing a proxy to the internal API
13+
* service.
14+
*
15+
* The route is only added in our non-production environments.
16+
*/
17+
const getSystemProxy = async (request, h) => {
18+
const service = new SystemProxyService(config.services.system, logger)
19+
let result
20+
21+
try {
22+
result = await service.getToPath(request.params.tail)
23+
} catch (error) {
24+
result = error.error
25+
}
26+
27+
return h.response(result)
28+
}
29+
30+
const getSystemJsProxy = async (request, h) => {
31+
let response
32+
33+
const service = new SystemProxyService(config.services.system, logger)
34+
35+
try {
36+
const result = await service.getToPath('assets/all.js')
37+
38+
response = h.response(result)
39+
.header('cache-control', 'no-cache')
40+
.type('application/javascript')
41+
} catch (error) {
42+
response = h.response().code(error.statusCode)
43+
}
44+
45+
return response
46+
}
47+
48+
const getSystemCssProxy = async (request, h) => {
49+
let response
50+
51+
const service = new SystemProxyService(config.services.system, logger)
52+
53+
try {
54+
const result = await service.getToPath('assets/stylesheets/application.css')
55+
56+
response = h.response(result)
57+
.header('cache-control', 'no-cache')
58+
.type('text/css')
59+
} catch (error) {
60+
response = h.response().code(error.statusCode)
61+
}
62+
63+
return response
64+
}
65+
66+
module.exports = {
67+
getSystemProxy,
68+
getSystemJsProxy,
69+
getSystemCssProxy
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const controller = require('./controller')
4+
5+
const routes = [
6+
{
7+
method: 'GET',
8+
path: '/system/{tail*}',
9+
handler: controller.getSystemProxy,
10+
config: {
11+
auth: false,
12+
description: 'Proxies requests to the Water Abstraction System'
13+
}
14+
},
15+
{
16+
method: 'GET',
17+
path: '/assets/all.js',
18+
handler: controller.getSystemJsProxy,
19+
config: {
20+
auth: false,
21+
description: 'Proxies JS asset requests to the Water Abstraction System'
22+
}
23+
},
24+
{
25+
method: 'GET',
26+
path: '/assets/stylesheets/application.css',
27+
handler: controller.getSystemCssProxy,
28+
config: {
29+
auth: false,
30+
description: 'Proxies CSS asset requests to the Water Abstraction System'
31+
}
32+
}
33+
]
34+
35+
module.exports = routes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
'use strict'
2+
3+
// Test framework dependencies
4+
const Lab = require('@hapi/lab')
5+
const Code = require('@hapi/code')
6+
const Sinon = require('sinon')
7+
8+
const { experiment, test, beforeEach, afterEach } = exports.lab = Lab.script()
9+
const { expect } = Code
10+
const sandbox = Sinon.createSandbox()
11+
12+
// Things we need to stub
13+
const SystemProxyService = require(
14+
'../../../../src/internal/lib/connectors/services/water/SystemProxyService'
15+
)
16+
17+
// Thing under test
18+
const controller = require('../../../../src/internal/modules/system-proxy/controller')
19+
20+
experiment('System proxy controller', () => {
21+
let request
22+
let h
23+
24+
let codeFake
25+
let typeFake
26+
let headerFake
27+
let responseFake
28+
29+
beforeEach(async () => {
30+
codeFake = sandbox.fake()
31+
typeFake = sandbox.fake()
32+
headerFake = sandbox.fake.returns({ type: typeFake })
33+
responseFake = sandbox.fake.returns({ header: headerFake, code: codeFake })
34+
35+
h = {
36+
response: responseFake
37+
}
38+
})
39+
40+
afterEach(() => {
41+
sandbox.restore()
42+
})
43+
44+
experiment('.getSystemProxy', () => {
45+
beforeEach(() => {
46+
request = {
47+
params: {
48+
tail: 'status'
49+
},
50+
payload: null
51+
}
52+
})
53+
54+
experiment('when the request is valid', () => {
55+
beforeEach(() => {
56+
sandbox.stub(SystemProxyService.prototype, 'getToPath').resolves('OK')
57+
})
58+
59+
test('returns whatever the system returns', async () => {
60+
await controller.getSystemProxy(request, h)
61+
62+
const result = responseFake.lastCall.args[0]
63+
64+
expect(result).to.equal('OK')
65+
})
66+
})
67+
68+
experiment('when the request is invalid', () => {
69+
let error
70+
71+
beforeEach(() => {
72+
error = {
73+
error: 'OH NO',
74+
statusCode: 404,
75+
message: '404 - "OH NO"',
76+
name: 'StatusCodeError'
77+
}
78+
sandbox.stub(SystemProxyService.prototype, 'getToPath').rejects(error)
79+
})
80+
81+
test('returns the system error details', async () => {
82+
await controller.getSystemProxy(request, h)
83+
84+
const result = responseFake.lastCall.args[0]
85+
86+
expect(result).to.equal(error.error)
87+
})
88+
})
89+
})
90+
91+
experiment('.getSystemJsProxy', () => {
92+
beforeEach(() => {
93+
request = {
94+
payload: null
95+
}
96+
})
97+
98+
experiment('when the request is valid', () => {
99+
beforeEach(() => {
100+
sandbox.stub(SystemProxyService.prototype, 'getToPath').resolves('OK')
101+
})
102+
103+
test('returns whatever the system returns', async () => {
104+
await controller.getSystemJsProxy(request, h)
105+
106+
const result = responseFake.lastCall.args[0]
107+
108+
expect(result).to.equal('OK')
109+
})
110+
111+
test('sets the expected header', async () => {
112+
await controller.getSystemJsProxy(request, h)
113+
114+
const result = headerFake.lastCall.args
115+
116+
expect(result).to.include(['cache-control', 'no-cache'])
117+
})
118+
119+
test('sets the expected type', async () => {
120+
await controller.getSystemJsProxy(request, h)
121+
122+
const result = typeFake.lastCall.args[0]
123+
124+
expect(result).to.equal('application/javascript')
125+
})
126+
})
127+
128+
experiment('when the request is invalid', () => {
129+
beforeEach(() => {
130+
const error = {
131+
statusCode: 404,
132+
message: 'OH NO'
133+
}
134+
sandbox.stub(SystemProxyService.prototype, 'getToPath').rejects(error)
135+
})
136+
137+
test('returns the system error status code', async () => {
138+
await controller.getSystemJsProxy(request, h)
139+
140+
const result = codeFake.lastCall.firstArg
141+
142+
expect(result).to.equal(404)
143+
})
144+
})
145+
})
146+
147+
experiment('.getSystemCssProxy', () => {
148+
beforeEach(() => {
149+
request = {
150+
payload: null
151+
}
152+
})
153+
154+
experiment('when the request is valid', () => {
155+
beforeEach(() => {
156+
sandbox.stub(SystemProxyService.prototype, 'getToPath').resolves('OK')
157+
})
158+
159+
test('returns whatever the system returns', async () => {
160+
await controller.getSystemCssProxy(request, h)
161+
162+
const result = responseFake.lastCall.args[0]
163+
164+
expect(result).to.equal('OK')
165+
})
166+
167+
test('sets the expected header', async () => {
168+
await controller.getSystemCssProxy(request, h)
169+
170+
const result = headerFake.lastCall.args
171+
172+
expect(result).to.include(['cache-control', 'no-cache'])
173+
})
174+
175+
test('sets the expected type', async () => {
176+
await controller.getSystemCssProxy(request, h)
177+
178+
const result = typeFake.lastCall.args[0]
179+
180+
expect(result).to.equal('text/css')
181+
})
182+
})
183+
184+
experiment('when the request is invalid', () => {
185+
beforeEach(() => {
186+
const error = {
187+
statusCode: 404,
188+
message: 'OH NO'
189+
}
190+
sandbox.stub(SystemProxyService.prototype, 'getToPath').rejects(error)
191+
})
192+
193+
test('returns the system error status code', async () => {
194+
await controller.getSystemCssProxy(request, h)
195+
196+
const result = codeFake.lastCall.firstArg
197+
198+
expect(result).to.equal(404)
199+
})
200+
})
201+
})
202+
})

0 commit comments

Comments
 (0)