import { test } from 'node:test' import assert from 'node:assert/strict' import crypto from 'node:crypto' import { encryptEnv, decryptEnv, validateEnvChange, envNames, maskLog, envKey, } from '../server/services/previewEnv.js' const key = crypto.randomBytes(32) test('Round-Trip und Format', () => { const s = encryptEnv({ A: 'x', B: 'ü' }, key) assert.match(s, /^v1:[^:]+:[^:]+:[^:]+$/) assert.deepEqual(decryptEnv(s, key), { A: 'x', B: 'ü' }) assert.notEqual(encryptEnv({ A: 'x' }, key), encryptEnv({ A: 'x' }, key)) }) test('leer', () => { assert.deepEqual(decryptEnv(encryptEnv({}, key), key), {}) assert.deepEqual(decryptEnv('', key), {}) assert.deepEqual(decryptEnv(null, key), {}) }) test('falscher Schluessel und manipulierter Tag => Fehler', () => { const s = encryptEnv({ A: 'geheim' }, key) assert.throws(() => decryptEnv(s, crypto.randomBytes(32))) const p = s.split(':') const tag = Buffer.from(p[2], 'base64') tag[0] ^= 1 p[2] = tag.toString('base64') assert.throws(() => decryptEnv(p.join(':'), key)) assert.throws(() => decryptEnv('v2:a:b:c', key)) }) test('envNames sortiert', () => { assert.deepEqual(envNames(encryptEnv({ B: '1', A: '2' }, key), key), ['A', 'B']) assert.deepEqual(envNames('', key), []) }) test('envKey', () => { const old = process.env.PREVIEW_ENV_KEY try { delete process.env.PREVIEW_ENV_KEY assert.equal(envKey(), null) process.env.PREVIEW_ENV_KEY = Buffer.alloc(16).toString('base64') assert.equal(envKey(), null) process.env.PREVIEW_ENV_KEY = key.toString('base64') assert.ok(envKey().equals(key)) } finally { if (old === undefined) delete process.env.PREVIEW_ENV_KEY else process.env.PREVIEW_ENV_KEY = old } }) test('validateEnvChange', () => { let r = validateEnvChange({ A: '1' }, { set: { B: '2' }, remove: ['A', 'NIX'] }) assert.deepEqual(r, { ok: true, env: { B: '2' } }) assert.equal(validateEnvChange({}, { set: { abc: '1' } }).ok, false) assert.equal(validateEnvChange({}, { set: { '1A': '1' } }).ok, false) assert.equal(validateEnvChange({}, { set: { A: 'x'.repeat(4097) } }).ok, false) assert.equal(validateEnvChange({}, { set: { A: 'x'.repeat(4096) } }).ok, true) assert.equal(validateEnvChange({}, { set: { A: 'ü'.repeat(2049) } }).ok, false) assert.equal(validateEnvChange({}, { set: { A: 1 } }).ok, false) assert.equal(validateEnvChange({}, { set: { A: 'a\nb' } }).ok, false) assert.equal(validateEnvChange({}, { set: { A: 'a\0b' } }).ok, false) const many = Object.fromEntries(Array.from({ length: 31 }, (_, i) => [`V${i}`, '1'])) r = validateEnvChange({}, { set: many }) assert.equal(r.ok, false) assert.match(r.error, /30/) const thirty = Object.fromEntries(Array.from({ length: 30 }, (_, i) => [`V${i}`, '1'])) assert.equal(validateEnvChange(thirty, { set: { V0: '2' } }).ok, true) assert.equal(validateEnvChange(thirty, { set: { NEU: '2' }, remove: ['V1'] }).ok, true) assert.equal(validateEnvChange(thirty, { set: { NEU: '2' } }).ok, false) assert.equal(validateEnvChange({}, {}).ok, true) }) test('maskLog', () => { const env = { A: 'geheim', B: 'abc', C: 'geheimer' , D: 'a.b+'} assert.equal(maskLog('x geheimer y geheim z abc a.b+ axbb', env), 'x ••• y ••• z abc ••• axbb') assert.equal(maskLog('', env), '') assert.equal(maskLog('text', {}), 'text') }) test('gekuerzter Tag, falsche IV-Laenge, manipulierter Ciphertext => Fehler', () => { const p = encryptEnv({ A: 'geheim' }, key).split(':') const b = (i) => Buffer.from(p[i], 'base64') const join = (x) => x.join(':') const short = [...p]; short[2] = b(2).subarray(0, 8).toString('base64') assert.throws(() => decryptEnv(join(short), key)) const iv = [...p]; iv[1] = b(1).subarray(0, 8).toString('base64') assert.throws(() => decryptEnv(join(iv), key)) const ct = [...p]; const c = b(3); c[0] ^= 1; ct[3] = c.toString('base64') assert.throws(() => decryptEnv(join(ct), key)) }) test('entschluesselter Inhalt muss Objekt mit String-Werten sein', () => { for (const bad of [['x'], 'text', { A: 1 }, null]) { const iv = crypto.randomBytes(12) const c = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 }) const ct = Buffer.concat([c.update(JSON.stringify(bad), 'utf8'), c.final()]) const s = ['v1', iv, c.getAuthTag(), ct].map((x) => (typeof x === 'string' ? x : x.toString('base64'))).join(':') assert.throws(() => decryptEnv(s, key)) } })