Both startupservice call sites pass null, and the attach guard skipped a null payload, so the service whose log a support ticket is read from never received browser_info / browser_url_vars. The e2e test named for the startup service only asserted on getdata, so the gap was invisible. The name is corrected to what it asserts, its sibling sweep now excludes the diagnostics services (it must, once the startup service really does carry them), and the guard itself is covered by a unit spec - red before the fix, green after.
166 lines
6.1 KiB
TypeScript
166 lines
6.1 KiB
TypeScript
// Marks this file as an ES module so its top-level consts don't collide with
|
|
// other spec files under the TS type-checker.
|
|
export {}
|
|
//
|
|
// The client sends `browser_info` (and `browser_url_vars`, when the page URL
|
|
// has parameters) with the startup service and with the services that execute
|
|
// customer-provided code - hook scripts and dynamic cell dropdown programs.
|
|
// Other services do not receive the tables.
|
|
//
|
|
// Payloads are keyed by the _program value in the request URL, so the
|
|
// assertions can tell one service's payload from another's. Asserted on the
|
|
// adapter interface, which is the only place the payload is observable end
|
|
// to end.
|
|
//
|
|
// Each test starts from a plain visit of the app root rather than assuming
|
|
// the previous test's session: Cypress clears cookies between tests, the
|
|
// SASjs Server session drops, and the app lands back on the evaluation
|
|
// agreement card. The acceptance is guarded, so it is a no-op when the
|
|
// session survived.
|
|
const hostUrl = Cypress.env('hosturl')
|
|
|
|
/** The services the app sends diagnostics to (see DIAGNOSTICS_SERVICES in
|
|
* client/src/app/services/sas.service.ts). */
|
|
const DIAGNOSTICS_SERVICES = [
|
|
'public/startupservice',
|
|
'editors/getdata',
|
|
'editors/getdynamiccolvals',
|
|
'editors/stagedata',
|
|
'editors/loadfile',
|
|
'editors/restore',
|
|
'auditors/postdata'
|
|
]
|
|
|
|
/** Boots the app at the root, accepting the evaluation agreement if shown. */
|
|
const bootApp = () => {
|
|
cy.visit(hostUrl, { timeout: 60000 })
|
|
cy.get('body').then(($body) => {
|
|
if ($body.find('#TCS input[type="checkbox"]').length) {
|
|
cy.get('#TCS input[type="checkbox"]').check({ force: true })
|
|
cy.wait(4000)
|
|
}
|
|
})
|
|
}
|
|
|
|
/** Captures each service's payload, keyed by the _program URL parameter. */
|
|
const capturePrograms = (): Record<string, string> => {
|
|
const programs: Record<string, string> = {}
|
|
cy.intercept('**/stp/execute/**', (req) => {
|
|
// The handler must never throw: an exception inside an intercept handler
|
|
// aborts the request, which breaks the app under test. A lone '%' in the
|
|
// URL would make decodeURIComponent throw, hence the guarded parse.
|
|
let program = req.url
|
|
try {
|
|
program = decodeURIComponent(
|
|
(req.url.match(/_program=([^&]+)/) || [])[1] || ''
|
|
)
|
|
} catch (e) {
|
|
program = (req.url.match(/_program=([^&]+)/) || [])[1] || req.url
|
|
}
|
|
if (typeof req.body === 'string') {
|
|
programs[program] = req.body
|
|
} else {
|
|
programs[program] = JSON.stringify(req.body)
|
|
}
|
|
})
|
|
return programs
|
|
}
|
|
|
|
context('browser_info input table: ', function () {
|
|
it('1 | services that run no customer code do not receive it', () => {
|
|
const programs = capturePrograms()
|
|
bootApp()
|
|
|
|
// The viewer route fires public/viewlibs and a usernav service - plain
|
|
// data services with no hook scripts and no customer includes. Taken
|
|
// from a fresh boot, before anything caches the library tree.
|
|
cy.visit(`${hostUrl}/#/view/data`, { timeout: 60000 })
|
|
cy.wait(8000)
|
|
|
|
cy.then(() => {
|
|
const captured = Object.keys(programs)
|
|
// The startup service is a diagnostics service by design - it runs on
|
|
// every boot, which is the point of it - so it is excluded from this
|
|
// sweep. On a real estate it would otherwise appear here carrying
|
|
// browser_info and fail the test.
|
|
const plain = captured.filter(
|
|
(program) => !DIAGNOSTICS_SERVICES.some((s) => program.includes(s))
|
|
)
|
|
expect(
|
|
plain.length,
|
|
`non-diagnostics service calls captured (${captured.join(', ')})`
|
|
).to.be.greaterThan(0)
|
|
|
|
plain.forEach((program) => {
|
|
expect(
|
|
programs[program],
|
|
`${program} carries no browser_info`
|
|
).not.to.contain('browser_info')
|
|
})
|
|
})
|
|
})
|
|
|
|
it('2 | hook-running services receive it', () => {
|
|
const programs = capturePrograms()
|
|
bootApp()
|
|
|
|
// Opening the editor directly makes the app call editors/getdata, which
|
|
// runs the pre-edit hook and so receives the diagnostics tables.
|
|
cy.visit(`${hostUrl}/#/editor/DC_JSLIB.MPE_X_TEST`, { timeout: 60000 })
|
|
cy.get('#hotTable', { timeout: 60000 }).should('exist')
|
|
cy.wait(8000)
|
|
|
|
cy.then(() => {
|
|
const captured = Object.keys(programs)
|
|
expect(
|
|
captured.length,
|
|
`service calls captured (${captured.join(', ')})`
|
|
).to.be.greaterThan(0)
|
|
|
|
// The startup service is the other diagnostics recipient, but it is not
|
|
// reachable from a mock estate: `checkSasjsDeploy` finds makedata in
|
|
// services/admin and routes to the deploy screen instead, so the app
|
|
// never calls it on boot. Its null-payload path is covered by the unit
|
|
// spec (src/app/services/sas.service.spec.ts).
|
|
const getdata = captured.find((p) => p.includes('editors/getdata'))
|
|
expect(getdata, 'editors/getdata captured').to.not.be.undefined
|
|
expect(
|
|
programs[getdata!],
|
|
'getdata payload carries browser_info'
|
|
).to.contain('browser_info')
|
|
// TIMEZONE is a column of browser_info and of no other input table, so
|
|
// this shows the row itself arrived, not just the table name.
|
|
expect(
|
|
programs[getdata!],
|
|
'browser_info row content in the payload'
|
|
).to.contain('timezone')
|
|
})
|
|
})
|
|
|
|
it('3 | browser_url_vars carries the URL parameters as name/value pairs', () => {
|
|
const programs = capturePrograms()
|
|
bootApp()
|
|
|
|
// The labels parameter is read by the editor from the hash query string,
|
|
// so the diagnostics tables must carry it as a name/value row.
|
|
cy.visit(`${hostUrl}/#/editor/DC_JSLIB.MPE_X_TEST?labels=true`, {
|
|
timeout: 60000
|
|
})
|
|
cy.get('#hotTable', { timeout: 60000 }).should('exist')
|
|
cy.wait(8000)
|
|
|
|
cy.then(() => {
|
|
const getdata = Object.keys(programs).find((p) =>
|
|
p.includes('editors/getdata')
|
|
)
|
|
expect(getdata, 'editors/getdata captured').to.not.be.undefined
|
|
expect(
|
|
programs[getdata!],
|
|
'getdata payload carries browser_url_vars'
|
|
).to.contain('browser_url_vars')
|
|
// the labels parameter, as a name in the name/value table
|
|
expect(programs[getdata!], 'labels parameter row').to.contain('labels')
|
|
})
|
|
})
|
|
})
|