@openpalm/lib 0.10.2 → 0.11.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +7 -3
- package/src/control-plane/admin-token.ts +73 -0
- package/src/control-plane/akm-vault.test.ts +105 -0
- package/src/control-plane/akm-vault.ts +307 -0
- package/src/control-plane/channels.ts +3 -3
- package/src/control-plane/cleanup-guardrails.test.ts +8 -9
- package/src/control-plane/compose-args.test.ts +25 -24
- package/src/control-plane/compose-errors.test.ts +106 -0
- package/src/control-plane/compose-errors.ts +117 -0
- package/src/control-plane/config-persistence.ts +103 -65
- package/src/control-plane/core-assets.test.ts +104 -0
- package/src/control-plane/core-assets.ts +54 -57
- package/src/control-plane/docker.ts +55 -21
- package/src/control-plane/env.test.ts +25 -1
- package/src/control-plane/env.ts +80 -0
- package/src/control-plane/home.ts +66 -69
- package/src/control-plane/host-opencode.test.ts +260 -0
- package/src/control-plane/host-opencode.ts +229 -0
- package/src/control-plane/install-edge-cases.test.ts +187 -289
- package/src/control-plane/install-lock.ts +157 -0
- package/src/control-plane/lifecycle.ts +34 -65
- package/src/control-plane/markdown-task.ts +200 -0
- package/src/control-plane/migrate-0110.test.ts +177 -0
- package/src/control-plane/migrate-0110.ts +99 -0
- package/src/control-plane/paths.ts +82 -0
- package/src/control-plane/provider-config.ts +2 -2
- package/src/control-plane/provider-models.ts +154 -0
- package/src/control-plane/registry-components.test.ts +105 -27
- package/src/control-plane/registry.test.ts +49 -47
- package/src/control-plane/registry.ts +71 -50
- package/src/control-plane/rollback.ts +17 -16
- package/src/control-plane/scheduler.ts +75 -262
- package/src/control-plane/secret-backend.test.ts +98 -111
- package/src/control-plane/secret-backend.ts +221 -181
- package/src/control-plane/secret-mappings.ts +4 -8
- package/src/control-plane/secrets.ts +93 -51
- package/src/control-plane/setup-config.schema.json +5 -17
- package/src/control-plane/setup-status.ts +9 -29
- package/src/control-plane/setup-validation.ts +23 -23
- package/src/control-plane/setup.test.ts +138 -239
- package/src/control-plane/setup.ts +215 -130
- package/src/control-plane/skeleton-guardrail.test.ts +151 -0
- package/src/control-plane/spec-to-env.test.ts +59 -58
- package/src/control-plane/spec-to-env.ts +52 -142
- package/src/control-plane/spec-validator.ts +2 -99
- package/src/control-plane/stack-spec.test.ts +21 -77
- package/src/control-plane/stack-spec.ts +7 -83
- package/src/control-plane/types.ts +12 -28
- package/src/control-plane/ui-assets.ts +349 -0
- package/src/control-plane/validate.ts +44 -79
- package/src/index.ts +86 -48
- package/src/logger.test.ts +228 -0
- package/src/logger.ts +71 -1
- package/src/provider-constants.ts +22 -1
- package/src/control-plane/audit.ts +0 -40
- package/src/control-plane/env-schema-validation.test.ts +0 -118
- package/src/control-plane/memory-config.ts +0 -298
- package/src/control-plane/redact-schema.ts +0 -50
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
-
import { lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { appendFileSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import {
|
|
@@ -8,39 +8,35 @@ import {
|
|
|
8
8
|
ensureSecrets,
|
|
9
9
|
validatePassEntryName,
|
|
10
10
|
} from '../index.js';
|
|
11
|
-
import { PlaintextBackend, PassBackend } from './secret-backend.js';
|
|
12
|
-
import { generateRedactSchema } from './redact-schema.js';
|
|
13
|
-
import { getCoreSecretMappings } from './secret-mappings.js';
|
|
14
11
|
import { writeSecretProviderConfig } from './provider-config.js';
|
|
12
|
+
import { akmUserVaultPathSync } from './akm-vault.js';
|
|
13
|
+
import { dirname } from 'node:path';
|
|
15
14
|
|
|
16
15
|
let rootDir = '';
|
|
17
16
|
|
|
18
17
|
function createState(): ControlPlaneState {
|
|
19
|
-
const
|
|
20
|
-
const dataDir = join(rootDir, 'data');
|
|
18
|
+
const stateDir = join(rootDir, 'state');
|
|
21
19
|
const configDir = join(rootDir, 'config');
|
|
22
|
-
const
|
|
20
|
+
const stackDir = join(configDir, 'stack');
|
|
23
21
|
const cacheDir = join(rootDir, 'cache');
|
|
24
|
-
mkdirSync(
|
|
25
|
-
mkdirSync(
|
|
22
|
+
mkdirSync(stateDir, { recursive: true });
|
|
23
|
+
mkdirSync(stackDir, { recursive: true });
|
|
26
24
|
mkdirSync(configDir, { recursive: true });
|
|
27
|
-
mkdirSync(
|
|
25
|
+
mkdirSync(join(rootDir, 'stash'), { recursive: true });
|
|
26
|
+
mkdirSync(join(rootDir, 'workspace'), { recursive: true });
|
|
28
27
|
mkdirSync(cacheDir, { recursive: true });
|
|
29
28
|
|
|
30
29
|
return {
|
|
31
|
-
adminToken: 'admin-token',
|
|
32
|
-
assistantToken: '',
|
|
33
|
-
setupToken: 'setup-token',
|
|
34
30
|
homeDir: rootDir,
|
|
35
31
|
configDir,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
logsDir,
|
|
32
|
+
stashDir: join(rootDir, 'stash'),
|
|
33
|
+
workspaceDir: join(rootDir, 'workspace'),
|
|
39
34
|
cacheDir,
|
|
35
|
+
stateDir,
|
|
36
|
+
stackDir,
|
|
40
37
|
services: {},
|
|
41
38
|
artifacts: { compose: '' },
|
|
42
39
|
artifactMeta: [],
|
|
43
|
-
audit: [],
|
|
44
40
|
};
|
|
45
41
|
}
|
|
46
42
|
|
|
@@ -55,11 +51,11 @@ afterEach(() => {
|
|
|
55
51
|
describe('secret backend', () => {
|
|
56
52
|
test('ensureSecrets repairs auth.json when Docker created it as a directory', () => {
|
|
57
53
|
const state = createState();
|
|
58
|
-
mkdirSync(join(state.
|
|
54
|
+
mkdirSync(join(state.configDir, "auth.json"), { recursive: true });
|
|
59
55
|
|
|
60
56
|
ensureSecrets(state);
|
|
61
57
|
|
|
62
|
-
const authJsonPath = join(state.
|
|
58
|
+
const authJsonPath = join(state.configDir, "auth.json");
|
|
63
59
|
expect(lstatSync(authJsonPath).isFile()).toBe(true);
|
|
64
60
|
expect(readFileSync(authJsonPath, 'utf-8')).toBe('{}\n');
|
|
65
61
|
});
|
|
@@ -70,6 +66,9 @@ describe('secret backend', () => {
|
|
|
70
66
|
const backend = detectSecretBackend(state);
|
|
71
67
|
|
|
72
68
|
expect(backend.provider).toBe('plaintext');
|
|
69
|
+
expect(backend.capabilities.generate).toBe(true);
|
|
70
|
+
expect(backend.capabilities.remove).toBe(true);
|
|
71
|
+
expect(backend.capabilities.rename).toBe(false);
|
|
73
72
|
|
|
74
73
|
const entry = await backend.write('openpalm/custom/example', 'very-secret');
|
|
75
74
|
expect(entry.provider).toBe('plaintext');
|
|
@@ -77,7 +76,7 @@ describe('secret backend', () => {
|
|
|
77
76
|
expect(await backend.exists('openpalm/custom/example')).toBe(true);
|
|
78
77
|
|
|
79
78
|
// Custom secrets are now written to stack.env (all secrets consolidated there)
|
|
80
|
-
const stackEnv = readFileSync(join(state.
|
|
79
|
+
const stackEnv = readFileSync(join(state.stackDir, "stack.env"), 'utf-8');
|
|
81
80
|
expect(stackEnv).toContain('very-secret');
|
|
82
81
|
});
|
|
83
82
|
|
|
@@ -109,11 +108,11 @@ describe('secret backend', () => {
|
|
|
109
108
|
});
|
|
110
109
|
});
|
|
111
110
|
|
|
112
|
-
describe('
|
|
111
|
+
describe('plaintext backend (via detectSecretBackend)', () => {
|
|
113
112
|
test('remove clears value for non-core secrets', async () => {
|
|
114
113
|
const state = createState();
|
|
115
114
|
ensureSecrets(state);
|
|
116
|
-
const backend =
|
|
115
|
+
const backend = detectSecretBackend(state);
|
|
117
116
|
|
|
118
117
|
await backend.write('openpalm/custom/temp', 'temp-value');
|
|
119
118
|
expect(await backend.exists('openpalm/custom/temp')).toBe(true);
|
|
@@ -132,7 +131,7 @@ describe('PlaintextBackend', () => {
|
|
|
132
131
|
test('remove clears value but keeps index for core secrets', async () => {
|
|
133
132
|
const state = createState();
|
|
134
133
|
ensureSecrets(state);
|
|
135
|
-
const backend =
|
|
134
|
+
const backend = detectSecretBackend(state);
|
|
136
135
|
|
|
137
136
|
// Write a core secret
|
|
138
137
|
await backend.write('openpalm/admin-token', 'my-token');
|
|
@@ -150,7 +149,7 @@ describe('PlaintextBackend', () => {
|
|
|
150
149
|
test('list includes both core and indexed entries', async () => {
|
|
151
150
|
const state = createState();
|
|
152
151
|
ensureSecrets(state);
|
|
153
|
-
const backend =
|
|
152
|
+
const backend = detectSecretBackend(state);
|
|
154
153
|
|
|
155
154
|
await backend.write('openpalm/custom/my-key', 'value');
|
|
156
155
|
|
|
@@ -166,16 +165,71 @@ describe('PlaintextBackend', () => {
|
|
|
166
165
|
test('generate creates a secret with random value', async () => {
|
|
167
166
|
const state = createState();
|
|
168
167
|
ensureSecrets(state);
|
|
169
|
-
const backend =
|
|
168
|
+
const backend = detectSecretBackend(state);
|
|
170
169
|
|
|
171
170
|
const entry = await backend.generate('openpalm/custom/generated', 64);
|
|
172
171
|
expect(entry.present).toBe(true);
|
|
173
172
|
expect(await backend.exists('openpalm/custom/generated')).toBe(true);
|
|
174
173
|
});
|
|
174
|
+
|
|
175
|
+
test('user-scope reads from akm vault, system-scope reads from stack.env', async () => {
|
|
176
|
+
// Regression test: user scope must consult the akm vault file, system scope
|
|
177
|
+
// must consult state/stack.env. When both files define the same key with
|
|
178
|
+
// different values, the two scopes must return their own file's value.
|
|
179
|
+
const state = createState();
|
|
180
|
+
ensureSecrets(state);
|
|
181
|
+
const backend = detectSecretBackend(state);
|
|
182
|
+
|
|
183
|
+
// Seed the akm vault file with a user-scope value.
|
|
184
|
+
const akmPath = akmUserVaultPathSync(state);
|
|
185
|
+
mkdirSync(dirname(akmPath), { recursive: true });
|
|
186
|
+
writeFileSync(akmPath, 'OPENAI_API_KEY=akm-vault-openai\n');
|
|
187
|
+
|
|
188
|
+
// Stack.env already exists from ensureSecrets — seed the system password.
|
|
189
|
+
const stackEnvPath = join(state.stackDir, "stack.env");
|
|
190
|
+
const stackContent = readFileSync(stackEnvPath, 'utf-8')
|
|
191
|
+
.replace(/^OP_UI_LOGIN_PASSWORD=.*$/m, 'OP_UI_LOGIN_PASSWORD=stack-login-password');
|
|
192
|
+
writeFileSync(stackEnvPath, stackContent);
|
|
193
|
+
|
|
194
|
+
// System scope reads stack.env exclusively.
|
|
195
|
+
expect(await backend.exists('openpalm/ui-login-password')).toBe(true);
|
|
196
|
+
const systemEntries = await backend.list('openpalm/ui-login-password');
|
|
197
|
+
expect(systemEntries.find((e) => e.key === 'openpalm/ui-login-password')?.present).toBe(true);
|
|
198
|
+
|
|
199
|
+
// User scope reads akm vault file.
|
|
200
|
+
const userEntries = await backend.list('openpalm/openai/');
|
|
201
|
+
const openai = userEntries.find((e) => e.key === 'openpalm/openai/api-key');
|
|
202
|
+
expect(openai).toBeDefined();
|
|
203
|
+
expect(openai?.scope).toBe('user');
|
|
204
|
+
expect(openai?.present).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('list/exists resolve user-scope secrets from akm vault', async () => {
|
|
208
|
+
// The backend MUST resolve user-managed secrets through the akm vault file
|
|
209
|
+
// (stash/vaults/user.env), not from any legacy path.
|
|
210
|
+
const state = createState();
|
|
211
|
+
ensureSecrets(state);
|
|
212
|
+
const backend = detectSecretBackend(state);
|
|
213
|
+
|
|
214
|
+
// Place the secret in the akm vault file.
|
|
215
|
+
const akmPath = akmUserVaultPathSync(state);
|
|
216
|
+
mkdirSync(dirname(akmPath), { recursive: true });
|
|
217
|
+
writeFileSync(akmPath, 'OPENAI_API_KEY=migrated-akm-value\n');
|
|
218
|
+
|
|
219
|
+
// exists() must report the user-scope secret as present.
|
|
220
|
+
expect(await backend.exists('openpalm/openai/api-key')).toBe(true);
|
|
221
|
+
|
|
222
|
+
// list() must enumerate it with present: true.
|
|
223
|
+
const entries = await backend.list('openpalm/openai/');
|
|
224
|
+
const openai = entries.find((e) => e.key === 'openpalm/openai/api-key');
|
|
225
|
+
expect(openai).toBeDefined();
|
|
226
|
+
expect(openai?.scope).toBe('user');
|
|
227
|
+
expect(openai?.present).toBe(true);
|
|
228
|
+
});
|
|
175
229
|
});
|
|
176
230
|
|
|
177
|
-
describe('
|
|
178
|
-
test('
|
|
231
|
+
describe('pass backend (via detectSecretBackend)', () => {
|
|
232
|
+
test('reports pass provider when configured', () => {
|
|
179
233
|
const state = createState();
|
|
180
234
|
writeSecretProviderConfig(state, {
|
|
181
235
|
provider: 'pass',
|
|
@@ -183,40 +237,42 @@ describe('PassBackend', () => {
|
|
|
183
237
|
passPrefix: 'myprefix',
|
|
184
238
|
});
|
|
185
239
|
|
|
186
|
-
const backend =
|
|
240
|
+
const backend = detectSecretBackend(state);
|
|
187
241
|
expect(backend.provider).toBe('pass');
|
|
188
|
-
// Verify it doesn't throw with valid config
|
|
189
242
|
expect(backend.capabilities.generate).toBe(true);
|
|
190
243
|
});
|
|
191
244
|
|
|
192
|
-
test('
|
|
245
|
+
test('uses default store dir when no config', () => {
|
|
193
246
|
const state = createState();
|
|
194
|
-
|
|
247
|
+
writeSecretProviderConfig(state, { provider: 'pass' });
|
|
248
|
+
const backend = detectSecretBackend(state);
|
|
195
249
|
expect(backend.provider).toBe('pass');
|
|
196
250
|
});
|
|
197
251
|
|
|
198
252
|
test('exists returns false for non-existent entries', async () => {
|
|
199
253
|
const state = createState();
|
|
200
|
-
const storeDir = join(rootDir, '
|
|
254
|
+
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
201
255
|
mkdirSync(storeDir, { recursive: true });
|
|
256
|
+
writeSecretProviderConfig(state, { provider: 'pass', passwordStoreDir: storeDir });
|
|
202
257
|
|
|
203
|
-
const backend =
|
|
258
|
+
const backend = detectSecretBackend(state);
|
|
204
259
|
expect(await backend.exists('openpalm/nonexistent')).toBe(false);
|
|
205
260
|
});
|
|
206
261
|
|
|
207
262
|
test('list returns empty array for empty store', async () => {
|
|
208
263
|
const state = createState();
|
|
209
|
-
const storeDir = join(rootDir, '
|
|
264
|
+
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
210
265
|
mkdirSync(storeDir, { recursive: true });
|
|
266
|
+
writeSecretProviderConfig(state, { provider: 'pass', passwordStoreDir: storeDir });
|
|
211
267
|
|
|
212
|
-
const backend =
|
|
268
|
+
const backend = detectSecretBackend(state);
|
|
213
269
|
const entries = await backend.list();
|
|
214
270
|
expect(entries).toEqual([]);
|
|
215
271
|
});
|
|
216
272
|
|
|
217
273
|
test('list scopes to passPrefix subdirectory', async () => {
|
|
218
274
|
const state = createState();
|
|
219
|
-
const storeDir = join(rootDir, '
|
|
275
|
+
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
220
276
|
|
|
221
277
|
// Create fake .gpg files under the prefix subdirectory
|
|
222
278
|
const prefixDir = join(storeDir, 'myprefix', 'openpalm');
|
|
@@ -234,7 +290,7 @@ describe('PassBackend', () => {
|
|
|
234
290
|
passPrefix: 'myprefix',
|
|
235
291
|
});
|
|
236
292
|
|
|
237
|
-
const backend =
|
|
293
|
+
const backend = detectSecretBackend(state);
|
|
238
294
|
const entries = await backend.list();
|
|
239
295
|
|
|
240
296
|
expect(entries).toHaveLength(2);
|
|
@@ -245,7 +301,7 @@ describe('PassBackend', () => {
|
|
|
245
301
|
|
|
246
302
|
test('exists checks prefixed path in store', async () => {
|
|
247
303
|
const state = createState();
|
|
248
|
-
const storeDir = join(rootDir, '
|
|
304
|
+
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
249
305
|
const prefixDir = join(storeDir, 'myprefix');
|
|
250
306
|
mkdirSync(join(prefixDir, 'openpalm'), { recursive: true });
|
|
251
307
|
writeFileSync(join(prefixDir, 'openpalm', 'admin-token.gpg'), 'fake');
|
|
@@ -256,21 +312,20 @@ describe('PassBackend', () => {
|
|
|
256
312
|
passPrefix: 'myprefix',
|
|
257
313
|
});
|
|
258
314
|
|
|
259
|
-
const backend =
|
|
315
|
+
const backend = detectSecretBackend(state);
|
|
260
316
|
expect(await backend.exists('openpalm/admin-token')).toBe(true);
|
|
261
317
|
expect(await backend.exists('openpalm/nonexistent')).toBe(false);
|
|
262
318
|
});
|
|
263
319
|
});
|
|
264
320
|
|
|
265
321
|
describe('detectSecretBackend', () => {
|
|
266
|
-
test('returns
|
|
322
|
+
test('returns plaintext provider by default', () => {
|
|
267
323
|
const state = createState();
|
|
268
324
|
const backend = detectSecretBackend(state);
|
|
269
325
|
expect(backend.provider).toBe('plaintext');
|
|
270
|
-
expect(backend).toBeInstanceOf(PlaintextBackend);
|
|
271
326
|
});
|
|
272
327
|
|
|
273
|
-
test('returns
|
|
328
|
+
test('returns pass provider when provider.json has provider: pass', () => {
|
|
274
329
|
const state = createState();
|
|
275
330
|
writeSecretProviderConfig(state, {
|
|
276
331
|
provider: 'pass',
|
|
@@ -279,81 +334,13 @@ describe('detectSecretBackend', () => {
|
|
|
279
334
|
|
|
280
335
|
const backend = detectSecretBackend(state);
|
|
281
336
|
expect(backend.provider).toBe('pass');
|
|
282
|
-
expect(backend).toBeInstanceOf(PassBackend);
|
|
283
337
|
});
|
|
284
338
|
|
|
285
|
-
test('returns
|
|
286
|
-
const state = createState();
|
|
287
|
-
mkdirSync(join(state.vaultDir, 'user'), { recursive: true });
|
|
288
|
-
writeFileSync(
|
|
289
|
-
join(state.vaultDir, 'user', 'user.env.schema'),
|
|
290
|
-
'# @plugin(@varlock/pass-plugin)\nOPENAI_API_KEY=pass("openpalm/openai/api-key")\n',
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const backend = detectSecretBackend(state);
|
|
294
|
-
expect(backend.provider).toBe('pass');
|
|
295
|
-
expect(backend).toBeInstanceOf(PassBackend);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('returns PlaintextBackend when provider.json has provider: plaintext', () => {
|
|
339
|
+
test('returns plaintext provider when provider.json has provider: plaintext', () => {
|
|
299
340
|
const state = createState();
|
|
300
341
|
writeSecretProviderConfig(state, { provider: 'plaintext' });
|
|
301
342
|
|
|
302
343
|
const backend = detectSecretBackend(state);
|
|
303
344
|
expect(backend.provider).toBe('plaintext');
|
|
304
|
-
expect(backend).toBeInstanceOf(PlaintextBackend);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe('generateRedactSchema', () => {
|
|
309
|
-
test('output includes all mapped env keys', () => {
|
|
310
|
-
const systemEnv: Record<string, string> = {};
|
|
311
|
-
const schema = generateRedactSchema(systemEnv);
|
|
312
|
-
|
|
313
|
-
// All static core mappings should be present
|
|
314
|
-
expect(schema).toContain('OP_ADMIN_TOKEN=');
|
|
315
|
-
expect(schema).toContain('OP_ASSISTANT_TOKEN=');
|
|
316
|
-
expect(schema).toContain('OP_MEMORY_TOKEN=');
|
|
317
|
-
expect(schema).toContain('OPENAI_API_KEY=');
|
|
318
|
-
expect(schema).toContain('ANTHROPIC_API_KEY=');
|
|
319
|
-
expect(schema).toContain('GROQ_API_KEY=');
|
|
320
|
-
expect(schema).toContain('MISTRAL_API_KEY=');
|
|
321
|
-
expect(schema).toContain('GOOGLE_API_KEY=');
|
|
322
|
-
expect(schema).toContain('MCP_API_KEY=');
|
|
323
|
-
expect(schema).toContain('EMBEDDING_API_KEY=');
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('includes legacy aliases', () => {
|
|
327
|
-
const schema = generateRedactSchema({});
|
|
328
|
-
expect(schema).toContain('ADMIN_TOKEN=');
|
|
329
|
-
expect(schema).toContain('OP_OPENCODE_PASSWORD=');
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test('includes dynamic channel secrets', () => {
|
|
333
|
-
const systemEnv = {
|
|
334
|
-
CHANNEL_DISCORD_SECRET: 'abc123',
|
|
335
|
-
CHANNEL_SLACK_SECRET: 'def456',
|
|
336
|
-
};
|
|
337
|
-
const schema = generateRedactSchema(systemEnv);
|
|
338
|
-
expect(schema).toContain('CHANNEL_DISCORD_SECRET=');
|
|
339
|
-
expect(schema).toContain('CHANNEL_SLACK_SECRET=');
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test('has correct header format', () => {
|
|
343
|
-
const schema = generateRedactSchema({});
|
|
344
|
-
expect(schema).toContain('@defaultSensitive=true');
|
|
345
|
-
expect(schema).toContain('@defaultRequired=false');
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
test('entries are sorted', () => {
|
|
349
|
-
const schema = generateRedactSchema({});
|
|
350
|
-
const lines = schema
|
|
351
|
-
.split('\n')
|
|
352
|
-
.filter((l) => l.match(/^[A-Z]/))
|
|
353
|
-
.map((l) => l.replace(/=.*$/, ''));
|
|
354
|
-
|
|
355
|
-
const sorted = [...lines].sort();
|
|
356
|
-
expect(lines).toEqual(sorted);
|
|
357
345
|
});
|
|
358
346
|
});
|
|
359
|
-
|