feat: Schlüssel und gemeinsamer Kern für Sprachnotizen (T7 L2 Tasks 5+6)
Task 5: Schlüsselbund-Zweck voice (VOICE_ENCRYPTION_KEY), AES-256-GCM- Hilfen für Text und Bytes mit AAD capture:<id>:<art>, voice-config (Modell, Tageslimit, Aufnahme, Whisper-Grösse) und anthropic-config (nie geloggt, nur worker/all), Startprüfung verlangt den Schlüssel in Produktion in jeder Rolle, rekey versiegelt offene Sprachnotizen neu (Audio einzeln gelesen, CAS mit Status), Secret-Vorlagen und ensure-secrets.sh ergänzt. Task 6: Text-Normalisierung und Bereinigung (Steuer- und Formatzeichen, Codepoint-Kürzung), Zitatprüfung mit Dezimalzahlen als ein Wort (Ruling 7), Felderliste passend zu CreateInspectionDto, versiegeltes Transkript mit Grenzen, Fachwörter und Skalen mit Paritätstests. Rollout: Ein Image mit diesem Stand startet in Produktion nur mit VOICE_ENCRYPTION_KEY in apicore-backend-secret (ARCHITECTURE Annahme 22). Gates: Backend tsc, eslint, 922 Unit-Tests, e2e 100 Suites / 1004 Tests. Refs #27 #28 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
2a4dc868d9
commit
9795f98006
27 files changed
+2212
-31
No files matched your search
@@ -0,0 +1,91 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { anthropicApiKey, assertAnthropicKey } from './anthropic-config';
|
||||
|
||||
const KEY = 'sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
function fakeLogger() {
|
||||
return { warn: jest.fn(), error: jest.fn() } satisfies Pick<
|
||||
Logger,
|
||||
'warn' | 'error'
|
||||
>;
|
||||
}
|
||||
|
||||
describe('anthropicApiKey', () => {
|
||||
it('returns a well-formed key, trimmed', () => {
|
||||
expect(anthropicApiKey({ ANTHROPIC_API_KEY: ` ${KEY} ` })).toBe(KEY);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['unset', {}],
|
||||
['empty', { ANTHROPIC_API_KEY: ' ' }],
|
||||
['a placeholder', { ANTHROPIC_API_KEY: 'change-me-anthropic-key' }],
|
||||
['the wrong format', { ANTHROPIC_API_KEY: 'sk-ant-short' }],
|
||||
['dummy', { ANTHROPIC_API_KEY: 'dummy' }],
|
||||
])('returns null for %s', (_label, env) => {
|
||||
expect(anthropicApiKey(env)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('assertAnthropicKey', () => {
|
||||
it('assertAnthropicKey never throws and disables on a placeholder', () => {
|
||||
for (const value of ['change-me-anthropic-key', 'dummy', '', 'sk-ant-x']) {
|
||||
const logger = fakeLogger();
|
||||
expect(() =>
|
||||
assertAnthropicKey(
|
||||
'worker',
|
||||
{ NODE_ENV: 'production', ANTHROPIC_API_KEY: value },
|
||||
logger,
|
||||
),
|
||||
).not.toThrow();
|
||||
}
|
||||
const logger = fakeLogger();
|
||||
assertAnthropicKey(
|
||||
'worker',
|
||||
{ NODE_ENV: 'production', ANTHROPIC_API_KEY: 'change-me-x' },
|
||||
logger,
|
||||
);
|
||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||
expect(anthropicApiKey({ ANTHROPIC_API_KEY: 'change-me-x' })).toBeNull();
|
||||
});
|
||||
|
||||
it('logs the variable name once, never the value', () => {
|
||||
const logger = fakeLogger();
|
||||
assertAnthropicKey(
|
||||
'worker',
|
||||
{ ANTHROPIC_API_KEY: 'sk-ant-short-secret' },
|
||||
logger,
|
||||
);
|
||||
const output = JSON.stringify([
|
||||
logger.warn.mock.calls,
|
||||
logger.error.mock.calls,
|
||||
]);
|
||||
expect(output).toContain('ANTHROPIC_API_KEY');
|
||||
expect(output).not.toContain('short-secret');
|
||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('api and job roles skip the Anthropic check', () => {
|
||||
for (const role of ['api', 'job'] as const) {
|
||||
const logger = fakeLogger();
|
||||
assertAnthropicKey(role, { ANTHROPIC_API_KEY: 'garbage' }, logger);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts a good key silently in the worker and in all-in-one mode', () => {
|
||||
for (const role of ['worker', 'all'] as const) {
|
||||
const logger = fakeLogger();
|
||||
assertAnthropicKey(role, { ANTHROPIC_API_KEY: KEY }, logger);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('warns that extraction stays off when the worker has no key', () => {
|
||||
const logger = fakeLogger();
|
||||
assertAnthropicKey('worker', {}, logger);
|
||||
expect(logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user