Files
dc/client/cypress/support/commands.ts
T
YuryShkoda 20ba633087 feat(licensing): support combined-key paste, object-format features, and a live key preview
- Accept a single pasted/uploaded combined licence key (base64+gzip of
  licence+activation key), auto-detected in either paste mode, alongside
  the legacy two-field format
- Warn and block applying a key generated for the wrong protocol before
  it ever reaches the backend
- Decode both the legacy positional features string and a new named
  features object, so already-issued keys keep working unchanged
- Show a live "Key Details" preview (validity, users, site IDs, enabled
  features) as a key is typed, pasted, or uploaded
- Fix cypress test setup broken by the paste-format default change, and
  an unreliable FileReader-based base64 conversion in the test helpers
2026-08-28 20:01:45 +01:00

274 lines
7.3 KiB
TypeScript

// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
import 'cypress-file-upload'
import { arrayBufferToBase64 } from './../util/helper-functions'
import moment from 'moment'
// These custom commands were added via Cypress.Commands.add() below but never
// had a type augmentation, so every spec calling cy.loginAndUpdateValidKey()/
// cy.isLoggedIn() had an unresolved-property error.
declare global {
namespace Cypress {
interface Chainable {
isLoggedIn(callback: (exist: boolean) => void): Chainable<void>
loginAndUpdateValidKey(forceLicenceKey?: boolean): Chainable<void>
}
}
}
const username = Cypress.env('username')
const password = Cypress.env('password')
const hostUrl = Cypress.env('hosturl')
const appLocation = Cypress.env('appLocation')
const longerCommandTimeout = Cypress.env('longerCommandTimeout')
const site_id_SASJS = Cypress.env('site_id_SASJS')
Cypress.Commands.add('isLoggedIn', (callback: (exist: boolean) => void) => {
cy.get('body').then(($body) => {
if ($body.find('.nav-tree').length > 0) {
if (callback) callback(true)
} else {
if (callback) callback(false)
}
})
})
Cypress.Commands.add(
'loginAndUpdateValidKey',
(forceLicenceKey: boolean = false) => {
cy.visit(hostUrl + appLocation)
cy.wait(2000)
cy.get('body').then(($body) => {
const usernameInput = $body.find('input.username')[0]
if (usernameInput && !Cypress.dom.isHidden(usernameInput)) {
cy.get('input.username').type(username)
cy.get('input.password').type(password)
cy.get('.login-group button').click()
}
cy.get('.app-loading', { timeout: longerCommandTimeout })
.should('not.exist')
.then(() => {
cy.wait(2000)
if ($body.find('.nav-tree').length > 0) {
/**
* If licence key is already working, then skip rest of the function
*/
return logout(() => {
return
})
} else {
const keyData = {
valid_until: moment().add(20, 'day').format('YYYY-MM-DD'),
number_of_users: 10,
hot_license_key: '',
site_id: '',
demo: false
}
return generateKeys(
keyData.valid_until,
keyData.number_of_users,
keyData.hot_license_key,
keyData.demo,
(keysGen: any) => {
return acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
if (!forceLicenceKey) {
return logout(() => {
return
})
} else {
return updateLicenseKeyQuick(keysGen, () => {
cy.wait(25000)
return acceptTermsIfPresented((result: boolean) => {
if (result) {
cy.wait(20000)
}
return logout(() => {
return
})
})
})
}
})
}
)
}
})
})
}
)
const logout = (callback?: any) => {
cy.get('.header-actions .dropdown-toggle')
.click()
.then(() => {
cy.get('.header-actions .dropdown-menu > .separator')
.next()
.click()
.then(() => {
if (callback) callback()
})
})
}
const updateLicenseKeyQuick = (keys: any, callback: any) => {
isLicensingPage((result: boolean) => {
if (!result) {
visitPage('licensing/update')
cy.wait(2000)
}
inputLicenseKeyPage(keys.licenseKey, keys.activationKey)
callback()
})
}
const acceptTermsIfPresented = (callback?: any) => {
cy.url().then((url: string) => {
if (url.includes('licensing/register')) {
cy.get('.card-block')
.scrollTo('bottom')
.then(() => {
cy.get('#checkbox1')
.click()
.then(() => {
if (callback) callback(true)
})
})
} else {
if (callback) callback(false)
}
})
}
const isLicensingPage = (callback: any) => {
cy.url().then((url: string) => {
callback(url.includes('licensing') && !url.includes('licensing/register'))
})
}
const inputLicenseKeyPage = (licenseKey: string, activationKey: string) => {
cy.get('button').contains('Paste licence').click()
// Combined single-string paste is the default format now - switch to
// the legacy two-field layout, since this helper fills licenseKey and
// activationKey as two separate values.
cy.get('button').contains('Paste as two separate keys instead').click()
cy.get('.license-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', licenseKey)
.trigger('input')
.should('not.be.undefined')
cy.get('.activation-key-form textarea', { timeout: longerCommandTimeout })
.invoke('val', activationKey)
.trigger('input')
.should('not.be.undefined')
cy.get('button.apply-keys').click()
}
const visitPage = (url: string) => {
cy.visit(`${hostUrl}${appLocation}/#/${url}`)
}
const generateKeys = async (
valid_until: string,
users_allowed: number,
hot_license_key: string,
demo: boolean,
resultCallback?: any
) => {
let keyPair = await window.crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2024,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256'
},
true,
['encrypt', 'decrypt']
)
let licenseData = {
valid_until: valid_until,
users_allowed: users_allowed,
hot_license_key: hot_license_key,
site_id: site_id_SASJS,
demo: demo
}
console.log('License data', licenseData)
let encoded = new TextEncoder().encode(JSON.stringify(licenseData))
console.log(encoded)
let cipher = await window.crypto.subtle
.encrypt(
{
name: 'RSA-OAEP'
},
keyPair.publicKey,
encoded
)
.then(
(value) => {
return value
},
(err) => {
console.log('Encrpyt error', err)
}
)
if (!cipher) {
alert('Encryptin keys failed')
throw new Error('Encryptin keys failed')
}
let privateKeyBytes = await window.crypto.subtle.exportKey(
'pkcs8',
keyPair.privateKey
)
let activationKey = await arrayBufferToBase64(privateKeyBytes)
let licenseKey = await arrayBufferToBase64(cipher)
if (resultCallback)
resultCallback({
activationKey,
licenseKey
})
}