Files
dc/client/src/app/licensing/utils/combinedLicenceKey.spec.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

91 lines
2.9 KiB
TypeScript

import * as base64Converter from 'base64-arraybuffer'
import {
COMBINED_KEY_PREFIX,
isCombinedLicenceKey,
splitCombinedLicenceKey
} from './combinedLicenceKey'
// Mirrors dckey's own encodeCombinedKey() (main.js) exactly, so these tests
// build fixtures the same way a real generated key would be produced,
// rather than relying on a hardcoded string that could go stale if either
// side's algorithm ever changes.
const gzipCompress = async (bytes: Uint8Array): Promise<ArrayBuffer> => {
const compressionStream = new CompressionStream('gzip')
const writer = compressionStream.writable.getWriter()
writer.write(new Uint8Array(bytes))
writer.close()
return new Response(compressionStream.readable).arrayBuffer()
}
const encodeCombinedLicenceKeyFixture = async (
licenceKey: string,
activationKey: string
): Promise<string> => {
const payloadBytes = new TextEncoder().encode(
`${licenceKey} ${activationKey}`
)
const compressedBytes = await gzipCompress(payloadBytes)
return COMBINED_KEY_PREFIX + base64Converter.encode(compressedBytes)
}
describe('isCombinedLicenceKey', () => {
it('is true for prefixed text', () => {
expect(isCombinedLicenceKey('DCKEY1:abc123')).toBeTrue()
})
it('is false for a plain legacy key', () => {
expect(isCombinedLicenceKey('some-legacy-licence-key')).toBeFalse()
})
it('is false for an empty string', () => {
expect(isCombinedLicenceKey('')).toBeFalse()
})
it('is true even with leading/trailing whitespace around the prefix', () => {
expect(isCombinedLicenceKey(' DCKEY1:abc123 ')).toBeTrue()
})
})
describe('splitCombinedLicenceKey', () => {
it('round-trips a fixture built the same way dckey encodes one', async () => {
const combined = await encodeCombinedLicenceKeyFixture(
'licence-key-value',
'activation-key-value'
)
expect(await splitCombinedLicenceKey(combined)).toEqual({
licenceKey: 'licence-key-value',
activationKey: 'activation-key-value'
})
})
it('returns null for text without the prefix', async () => {
expect(
await splitCombinedLicenceKey('a-plain-legacy-licence-key')
).toBeNull()
})
it('rejects for prefixed text that is not valid compressed data', async () => {
await expectAsync(
splitCombinedLicenceKey('DCKEY1:not-valid-base64-gzip-data')
).toBeRejected()
})
it('splits on the first space only, not every space', async () => {
// Not a real-world value today (licence/activation keys are base64,
// which never contains a space) - guards the split logic itself rather
// than that assumption.
const combined = await encodeCombinedLicenceKeyFixture(
'licence key with spaces',
'activation-key-value'
)
expect(await splitCombinedLicenceKey(combined)).toEqual({
licenceKey: 'licence',
activationKey: 'key with spaces activation-key-value'
})
})
})