// 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 => { const programs: Record = {} 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') }) }) })