1 |
|
2 | 'use strict'
|
3 |
|
4 | const chai = require('chai')
|
5 | const expect = chai.expect
|
6 | const tymly = require('@wmfs/tymly')
|
7 | const path = require('path')
|
8 | const HlPgClient = require('@wmfs/hl-pg-client')
|
9 | const process = require('process')
|
10 | const sqlScriptRunner = require('./fixtures/sql-script-runner.js')
|
11 | const moment = require('moment')
|
12 | const WEIGHTS_TO_UPDATE = require('./fixtures/updated-weights.json')
|
13 |
|
14 | process.on('unhandledRejection', (reason, p) => {
|
15 | console.log('Unhandled Rejection at: Promise', p, 'reason:', reason)
|
16 |
|
17 | })
|
18 |
|
19 | describe('Tests the Ranking State Resource', function () {
|
20 | this.timeout(15000)
|
21 |
|
22 | const REFRESH_ALL_STATE_MACHINE_NAME = 'wmfs_refreshAll_1_0'
|
23 | const REFRESH_STATE_MACHINE_NAME = 'test_refreshRanking_1_0'
|
24 | const SET_REFRESH_STATE_MACHINE_NAME = 'wmfs_setAndRefresh_1_0'
|
25 | const REFRESH_RISK_STATE_MACHINE_NAME = 'test_refreshRiskScore_1_0'
|
26 |
|
27 | const originalScores = []
|
28 |
|
29 | let statebox, tymlyService, rankingService, rankingModel, refreshModel
|
30 | const AuditDate = () => moment([2018, 5, 18])
|
31 | let TestTimestamp
|
32 |
|
33 |
|
34 |
|
35 | const pgConnectionString = process.env.PG_CONNECTION_STRING
|
36 | const client = new HlPgClient(pgConnectionString)
|
37 |
|
38 | before(function () {
|
39 | if (process.env.PG_CONNECTION_STRING && !/^postgres:\/\/[^:]+:[^@]+@(?:localhost|127\.0\.0\.1).*$/.test(process.env.PG_CONNECTION_STRING)) {
|
40 | console.log(`Skipping tests due to unsafe PG_CONNECTION_STRING value (${process.env.PG_CONNECTION_STRING})`)
|
41 | this.skip()
|
42 | }
|
43 | })
|
44 |
|
45 | describe('setup', () => {
|
46 | it('create test resources', () => {
|
47 | return sqlScriptRunner('./db-scripts/setup.sql', client)
|
48 | })
|
49 |
|
50 | it('start tymly', done => {
|
51 | tymly.boot(
|
52 | {
|
53 | pluginPaths: [
|
54 | path.resolve(__dirname, './..'),
|
55 | require.resolve('@wmfs/tymly-pg-plugin'),
|
56 | require.resolve('@wmfs/tymly-test-helpers/plugins/mock-users-plugin')
|
57 | ],
|
58 | blueprintPaths: [
|
59 | path.resolve(__dirname, './fixtures/blueprint'),
|
60 | path.resolve(__dirname, '../lib/blueprints/ranking-blueprint')
|
61 | ],
|
62 | config: {}
|
63 | },
|
64 | (err, tymlyServices) => {
|
65 | expect(err).to.eql(null)
|
66 | tymlyService = tymlyServices.tymly
|
67 | rankingService = tymlyServices.rankings
|
68 | statebox = tymlyServices.statebox
|
69 | rankingModel = tymlyServices.storage.models.test_rankingUprns
|
70 | refreshModel = tymlyServices.storage.models.wmfs_rankingRefreshStatus
|
71 | tymlyServices.timestamp.timeProvider = {
|
72 | today () {
|
73 | return moment(TestTimestamp)
|
74 | }
|
75 | }
|
76 |
|
77 | done()
|
78 | }
|
79 | )
|
80 | })
|
81 |
|
82 | it('should refresh all rankings', async () => {
|
83 | const execDesc = await statebox.startExecution({}, REFRESH_ALL_STATE_MACHINE_NAME, { sendResponse: 'COMPLETE' })
|
84 | expect(execDesc.status).to.eql('SUCCEEDED')
|
85 | })
|
86 |
|
87 | it('verify factory data', async () => {
|
88 | const viewData = await client.query('select * from test.factory_scores')
|
89 | const rankingData = await rankingModel.find({
|
90 | where: {
|
91 | rankingName: { equals: 'factory' }
|
92 | }
|
93 | })
|
94 |
|
95 | const _statsData = await client.query('select * from test.ranking_uprns_stats where category = \'factory\'')
|
96 | const statsData = _statsData.rows[0]
|
97 |
|
98 | const mergedData = rankingData
|
99 | .map((r, i) => {
|
100 | return {
|
101 | uprn: r.uprn,
|
102 | score: viewData.rows[i].updated_risk_score || viewData.rows[i].original_risk_score,
|
103 | range: r.range
|
104 | }
|
105 | })
|
106 | .sort((b, c) => {
|
107 | return b.score - c.score
|
108 | })
|
109 |
|
110 | expect(mergedData[0].range).to.eql('very-low')
|
111 | expect(mergedData[mergedData.length - 1].range).to.eql('very-high')
|
112 |
|
113 | originalScores.push(mergedData[0], mergedData[mergedData.length - 1])
|
114 |
|
115 | expect(statsData.ranges.veryLow.lowerBound).to.eql(0)
|
116 | expect(statsData.ranges.veryHigh.upperBound).to.eql(mergedData[mergedData.length - 1].score)
|
117 | expect(+statsData.count).to.eql(13)
|
118 | expect(+statsData.mean).to.eql(58.23)
|
119 | expect(+statsData.stdev).to.eql(31.45)
|
120 | })
|
121 |
|
122 | it('test the service function to find range', async () => {
|
123 | const viewRes = await client.query('select * from test.factory_scores;')
|
124 | const { original_risk_score: originalRiskScore, updated_risk_score: updatedRiskScore } = viewRes.rows[0]
|
125 | const score = updatedRiskScore || originalRiskScore
|
126 | const range = await rankingService.findRange('test.ranking_uprns_stats', 'factory', score)
|
127 | expect(range).to.eql('veryHigh')
|
128 | })
|
129 |
|
130 | it('test the service function to find distribution', async () => {
|
131 | const viewRes = await client.query('select * from test.factory_scores;')
|
132 | const { original_risk_score: originalRiskScore, updated_risk_score: updatedRiskScore } = viewRes.rows[0]
|
133 | const score = updatedRiskScore || originalRiskScore
|
134 | const dist = await rankingService.findDistribution('test.ranking_uprns_stats', 'factory', score)
|
135 | expect(dist).to.eql(0.0016)
|
136 | })
|
137 | })
|
138 |
|
139 | describe('calculated risk scores', () => {
|
140 | describe('update with fire safety level', () => {
|
141 | it('update the ranking model with fire safety level', async () => {
|
142 | await rankingModel.upsert({
|
143 | uprn: originalScores[0].uprn,
|
144 | rankingName: 'factory',
|
145 | fsManagement: 'high',
|
146 | lastAuditDate: AuditDate(),
|
147 | lastEnforcementAction: 'SATISFACTORY'
|
148 | }, {})
|
149 | await rankingModel.upsert({
|
150 | uprn: originalScores[1].uprn,
|
151 | rankingName: 'factory',
|
152 | fsManagement: 'veryLow',
|
153 | lastAuditDate: AuditDate(),
|
154 | lastEnforcementAction: 'ENFORCEMENT'
|
155 | }, {})
|
156 |
|
157 |
|
158 | const a = await client.query(`select original_risk_score from test.factory_scores where uprn = ${originalScores[0].uprn}`)
|
159 | const b = await client.query(`select original_risk_score from test.factory_scores where uprn = ${originalScores[1].uprn}`)
|
160 |
|
161 |
|
162 |
|
163 |
|
164 | expect(a.rows[0].original_risk_score).to.eql(originalScores[0].score + 4)
|
165 |
|
166 |
|
167 |
|
168 |
|
169 | expect(b.rows[0].original_risk_score).to.eql(originalScores[1].score + 124)
|
170 |
|
171 |
|
172 | originalScores[0].score = a.rows[0].original_risk_score
|
173 | originalScores[1].score = b.rows[0].original_risk_score
|
174 | })
|
175 | })
|
176 |
|
177 | const refreshTests = [
|
178 | {
|
179 | days: 0,
|
180 | a_score: 13,
|
181 | b_score: 62.62
|
182 | },
|
183 | {
|
184 | days: 365,
|
185 | a_score: 15.34,
|
186 | b_score: 146.42
|
187 | },
|
188 | {
|
189 | days: 730,
|
190 | a_score: 17.54,
|
191 | b_score: 212.45
|
192 | },
|
193 | {
|
194 | days: 1460,
|
195 | a_score: 21.10,
|
196 | b_score: 243.92
|
197 | }
|
198 | ]
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 | for (const rt of refreshTests) {
|
223 | describe(`audit was ${rt.days} ago`, () => {
|
224 | it('refresh ranking', async () => {
|
225 | TestTimestamp = AuditDate().add(rt.days, 'days')
|
226 |
|
227 | const execDesc = await statebox.startExecution(
|
228 | { schema: 'test', category: 'factory' },
|
229 | REFRESH_STATE_MACHINE_NAME,
|
230 | { sendResponse: 'COMPLETE' }
|
231 | )
|
232 | expect(execDesc.status).to.eql('SUCCEEDED')
|
233 | })
|
234 | it('check ranking refresh status model', async () => {
|
235 | const refreshRecords = await refreshModel.find({ where: { key: { equals: 'test_factory' } } })
|
236 | expect(refreshRecords[0].status).to.eql('ENDED')
|
237 | })
|
238 | it(`verify calculated risk score on day ${rt.days}`, async () => {
|
239 | const a = await rankingModel.findById(originalScores[0].uprn)
|
240 | const b = await rankingModel.findById(originalScores[1].uprn)
|
241 |
|
242 | expect(+a.updatedRiskScore).to.eql(rt.a_score)
|
243 | expect(+b.updatedRiskScore).to.eql(rt.b_score)
|
244 | })
|
245 |
|
246 | it('verify projected dates', async () => {
|
247 | const a = await rankingModel.findById(originalScores[0].uprn)
|
248 | const b = await rankingModel.findById(originalScores[1].uprn)
|
249 |
|
250 |
|
251 | expect(a.projectedHighRiskCrossover).to.eql(null)
|
252 | expect(moment(a.projectedReturnToOriginal).format('YYYY-MM-DD')).to.eql('2027-04-11')
|
253 |
|
254 | expect(moment(b.projectedHighRiskCrossover).format('YYYY-MM-DD')).to.eql('2019-03-22')
|
255 | expect(moment(b.projectedReturnToOriginal).format('YYYY-MM-DD')).to.eql('2022-12-17')
|
256 | })
|
257 | })
|
258 | }
|
259 | })
|
260 |
|
261 | describe('stats view', () => {
|
262 | it('verify factory stats', async () => {
|
263 | const _statsData = await client.query('select * from test.ranking_uprns_stats where category = \'factory\'')
|
264 | const statsData = _statsData.rows[0]
|
265 |
|
266 | expect(+statsData.count).to.eql(13)
|
267 | expect(+statsData.mean).to.eql(68.08)
|
268 | expect(+statsData.stdev).to.eql(57.18)
|
269 | expect(+statsData.variance).to.eql(3270.07)
|
270 | })
|
271 | it('verify hotel stats', async () => {
|
272 | const _statsData = await client.query('select * from test.ranking_uprns_stats where category = \'hotel\'')
|
273 | const statsData = _statsData.rows[0]
|
274 |
|
275 | expect(+statsData.count).to.eql(1)
|
276 | expect(+statsData.mean).to.eql(18)
|
277 | expect(+statsData.stdev).to.eql(0)
|
278 | expect(+statsData.variance).to.eql(0)
|
279 | })
|
280 | it('verify shop stats', async () => {
|
281 | const _statsData = await client.query('select * from test.ranking_uprns_stats where category = \'shop\'')
|
282 | const statsData = _statsData.rows[0]
|
283 |
|
284 | expect(+statsData.count).to.eql(0)
|
285 | expect(+statsData.mean).to.eql(0)
|
286 | expect(+statsData.stdev).to.eql(0)
|
287 | expect(+statsData.variance).to.eql(0)
|
288 | })
|
289 | })
|
290 |
|
291 | describe('rankings view', () => {
|
292 | it('run set and refresh state machine', async () => {
|
293 |
|
294 | const execDesc = await statebox.startExecution(
|
295 | {
|
296 | setRegistryKey: {
|
297 | key: 'test_factory',
|
298 | value: WEIGHTS_TO_UPDATE
|
299 | },
|
300 | refreshRanking: {
|
301 | schema: 'test',
|
302 | category: 'factory'
|
303 | }
|
304 | },
|
305 | SET_REFRESH_STATE_MACHINE_NAME,
|
306 | { sendResponse: 'COMPLETE' }
|
307 | )
|
308 | expect(execDesc.status).to.eql('SUCCEEDED')
|
309 | })
|
310 |
|
311 | it('verify view data has been adjusted', async () => {
|
312 | const a = await client.query(`select * from test.factory_scores where uprn = ${originalScores[0].uprn}`)
|
313 | const b = await client.query(`select * from test.factory_scores where uprn = ${originalScores[1].uprn}`)
|
314 |
|
315 |
|
316 | expect(a.rows[0].last_enforcement_score).to.eql(-8)
|
317 | expect(a.rows[0].original_risk_score).to.eql(originalScores[0].score + 8)
|
318 |
|
319 |
|
320 | expect(b.rows[0].last_enforcement_score).to.eql(64)
|
321 | expect(b.rows[0].original_risk_score).to.eql(originalScores[1].score)
|
322 | })
|
323 | })
|
324 |
|
325 | describe('refresh risk scores state machine', () => {
|
326 | it('refresh risk score', async () => {
|
327 | const execDesc = await statebox.startExecution(
|
328 | {
|
329 | schema: 'test',
|
330 | category: 'factory',
|
331 | uprn: 1
|
332 | },
|
333 | REFRESH_RISK_STATE_MACHINE_NAME,
|
334 | { sendResponse: 'COMPLETE' }
|
335 | )
|
336 | expect(execDesc.status).to.eql('SUCCEEDED')
|
337 | })
|
338 |
|
339 | it('refresh ranking without passing in any schema/category', async () => {
|
340 | const execDesc = await statebox.startExecution(
|
341 | {},
|
342 | REFRESH_STATE_MACHINE_NAME,
|
343 | { sendResponse: 'COMPLETE' }
|
344 | )
|
345 |
|
346 | expect(execDesc.status).to.eql('FAILED')
|
347 | expect(execDesc.errorCode).to.eql('noSchemaOrCategory')
|
348 | expect(execDesc.errorMessage).to.eql('No schema or category on input.')
|
349 | })
|
350 | })
|
351 |
|
352 | describe('clean up', () => {
|
353 | it('clean up test resources', () => {
|
354 | return sqlScriptRunner('./db-scripts/cleanup.sql', client)
|
355 | })
|
356 |
|
357 | it('shutdown Tymly', async () => {
|
358 | await tymlyService.shutdown()
|
359 | })
|
360 |
|
361 | it('close database connections', function (done) {
|
362 | client.end()
|
363 | done()
|
364 | })
|
365 | })
|
366 | })
|