@openpalm/lib 0.11.0-beta.1 → 0.11.0-beta.10
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 -0
- package/package.json +5 -1
- package/src/control-plane/akm-vault.test.ts +1 -4
- package/src/control-plane/akm-vault.ts +5 -1
- package/src/control-plane/channels.ts +8 -6
- package/src/control-plane/compose-args.test.ts +0 -12
- package/src/control-plane/compose-args.ts +0 -4
- 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 +49 -13
- package/src/control-plane/core-assets.ts +63 -7
- package/src/control-plane/docker.ts +15 -4
- package/src/control-plane/env.ts +4 -1
- package/src/control-plane/host-opencode.test.ts +0 -3
- package/src/control-plane/install-edge-cases.test.ts +29 -69
- package/src/control-plane/lifecycle.ts +39 -50
- package/src/control-plane/migrate-0110.test.ts +177 -0
- package/src/control-plane/migrate-0110.ts +99 -0
- package/src/control-plane/operator-ids.test.ts +130 -0
- package/src/control-plane/operator-ids.ts +89 -0
- package/src/control-plane/paths.ts +8 -3
- package/src/control-plane/registry-components.test.ts +3 -2
- package/src/control-plane/registry.test.ts +198 -4
- package/src/control-plane/registry.ts +333 -4
- package/src/control-plane/secret-mappings.ts +2 -3
- package/src/control-plane/secrets.ts +17 -11
- package/src/control-plane/setup-config.schema.json +3 -3
- package/src/control-plane/setup-status.ts +6 -1
- package/src/control-plane/setup-validation.ts +2 -2
- package/src/control-plane/setup.test.ts +24 -20
- package/src/control-plane/setup.ts +25 -41
- package/src/control-plane/spec-to-env.test.ts +30 -16
- package/src/control-plane/spec-to-env.ts +37 -21
- package/src/control-plane/stack-spec.test.ts +5 -11
- package/src/control-plane/stack-spec.ts +2 -6
- package/src/control-plane/types.ts +0 -22
- package/src/control-plane/ui-assets.ts +45 -9
- package/src/control-plane/validate.ts +1 -1
- package/src/index.ts +26 -13
- package/src/logger.test.ts +12 -12
- package/src/logger.ts +1 -1
- package/src/control-plane/admin-token.ts +0 -73
- package/src/control-plane/audit.ts +0 -41
- package/src/control-plane/lock.test.ts +0 -194
- package/src/control-plane/lock.ts +0 -176
- package/src/control-plane/provider-config.ts +0 -34
- package/src/control-plane/secret-backend.test.ts +0 -349
- package/src/control-plane/secret-backend.ts +0 -362
- package/src/control-plane/spec-validator.ts +0 -62
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import type { ControlPlaneState } from './types.js';
|
|
3
|
-
|
|
4
|
-
export type SecretProviderConfig = {
|
|
5
|
-
provider: 'plaintext' | 'pass';
|
|
6
|
-
passwordStoreDir?: string;
|
|
7
|
-
passPrefix?: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
function providerConfigPath(state: ControlPlaneState): string {
|
|
11
|
-
return `${state.stateDir}/secrets/provider.json`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function readSecretProviderConfig(state: ControlPlaneState): SecretProviderConfig | null {
|
|
15
|
-
const path = providerConfigPath(state);
|
|
16
|
-
if (!existsSync(path)) return null;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const parsed = JSON.parse(readFileSync(path, 'utf-8')) as SecretProviderConfig;
|
|
20
|
-
if (parsed?.provider === 'plaintext' || parsed?.provider === 'pass') {
|
|
21
|
-
return parsed;
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
// ignore malformed provider config and fall back to schema detection
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function writeSecretProviderConfig(state: ControlPlaneState, config: SecretProviderConfig): void {
|
|
31
|
-
const dir = `${state.stateDir}/secrets`;
|
|
32
|
-
mkdirSync(dir, { recursive: true });
|
|
33
|
-
writeFileSync(providerConfigPath(state), JSON.stringify(config, null, 2) + '\n');
|
|
34
|
-
}
|
|
@@ -1,349 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
2
|
-
import { appendFileSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import {
|
|
6
|
-
detectSecretBackend,
|
|
7
|
-
type ControlPlaneState,
|
|
8
|
-
ensureSecrets,
|
|
9
|
-
validatePassEntryName,
|
|
10
|
-
} from '../index.js';
|
|
11
|
-
import { writeSecretProviderConfig } from './provider-config.js';
|
|
12
|
-
import { akmUserVaultPathSync } from './akm-vault.js';
|
|
13
|
-
import { dirname } from 'node:path';
|
|
14
|
-
|
|
15
|
-
let rootDir = '';
|
|
16
|
-
|
|
17
|
-
function createState(): ControlPlaneState {
|
|
18
|
-
const stateDir = join(rootDir, 'state');
|
|
19
|
-
const configDir = join(rootDir, 'config');
|
|
20
|
-
const stackDir = join(configDir, 'stack');
|
|
21
|
-
const cacheDir = join(rootDir, 'cache');
|
|
22
|
-
mkdirSync(stateDir, { recursive: true });
|
|
23
|
-
mkdirSync(stackDir, { recursive: true });
|
|
24
|
-
mkdirSync(configDir, { recursive: true });
|
|
25
|
-
mkdirSync(join(rootDir, 'stash'), { recursive: true });
|
|
26
|
-
mkdirSync(join(rootDir, 'workspace'), { recursive: true });
|
|
27
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
adminToken: 'admin-token',
|
|
31
|
-
assistantToken: '',
|
|
32
|
-
homeDir: rootDir,
|
|
33
|
-
configDir,
|
|
34
|
-
stashDir: join(rootDir, 'stash'),
|
|
35
|
-
workspaceDir: join(rootDir, 'workspace'),
|
|
36
|
-
cacheDir,
|
|
37
|
-
stateDir,
|
|
38
|
-
stackDir,
|
|
39
|
-
services: {},
|
|
40
|
-
artifacts: { compose: '' },
|
|
41
|
-
artifactMeta: [],
|
|
42
|
-
audit: [],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
rootDir = mkdtempSync(join(tmpdir(), 'openpalm-secret-backend-'));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
afterEach(() => {
|
|
51
|
-
rmSync(rootDir, { recursive: true, force: true });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe('secret backend', () => {
|
|
55
|
-
test('ensureSecrets repairs auth.json when Docker created it as a directory', () => {
|
|
56
|
-
const state = createState();
|
|
57
|
-
mkdirSync(join(state.configDir, "auth.json"), { recursive: true });
|
|
58
|
-
|
|
59
|
-
ensureSecrets(state);
|
|
60
|
-
|
|
61
|
-
const authJsonPath = join(state.configDir, "auth.json");
|
|
62
|
-
expect(lstatSync(authJsonPath).isFile()).toBe(true);
|
|
63
|
-
expect(readFileSync(authJsonPath, 'utf-8')).toBe('{}\n');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('detectSecretBackend defaults to plaintext and routes custom secrets into vault env files', async () => {
|
|
67
|
-
const state = createState();
|
|
68
|
-
ensureSecrets(state);
|
|
69
|
-
const backend = detectSecretBackend(state);
|
|
70
|
-
|
|
71
|
-
expect(backend.provider).toBe('plaintext');
|
|
72
|
-
expect(backend.capabilities.generate).toBe(true);
|
|
73
|
-
expect(backend.capabilities.remove).toBe(true);
|
|
74
|
-
expect(backend.capabilities.rename).toBe(false);
|
|
75
|
-
|
|
76
|
-
const entry = await backend.write('openpalm/custom/example', 'very-secret');
|
|
77
|
-
expect(entry.provider).toBe('plaintext');
|
|
78
|
-
expect(entry.scope).toBe('user');
|
|
79
|
-
expect(await backend.exists('openpalm/custom/example')).toBe(true);
|
|
80
|
-
|
|
81
|
-
// Custom secrets are now written to stack.env (all secrets consolidated there)
|
|
82
|
-
const stackEnv = readFileSync(join(state.stackDir, "stack.env"), 'utf-8');
|
|
83
|
-
expect(stackEnv).toContain('very-secret');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
test('validatePassEntryName rejects traversal and invalid characters', () => {
|
|
87
|
-
expect(() => validatePassEntryName('../bad')).toThrow();
|
|
88
|
-
expect(() => validatePassEntryName('openpalm/Bad Key')).toThrow();
|
|
89
|
-
expect(validatePassEntryName('openpalm/custom/good-key')).toBe('openpalm/custom/good-key');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('validatePassEntryName rejects empty after trim', () => {
|
|
93
|
-
expect(() => validatePassEntryName('')).toThrow('must not be empty');
|
|
94
|
-
expect(() => validatePassEntryName(' ')).toThrow('must not be empty');
|
|
95
|
-
expect(() => validatePassEntryName('///')).toThrow('must not be empty');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('validatePassEntryName rejects uppercase characters', () => {
|
|
99
|
-
expect(() => validatePassEntryName('openpalm/MyKey')).toThrow('invalid characters');
|
|
100
|
-
expect(() => validatePassEntryName('OPENPALM/key')).toThrow('invalid characters');
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('validatePassEntryName handles multiple slashes and dots', () => {
|
|
104
|
-
expect(validatePassEntryName('openpalm/a/b/c')).toBe('openpalm/a/b/c');
|
|
105
|
-
expect(validatePassEntryName('openpalm/my.key')).toBe('openpalm/my.key');
|
|
106
|
-
expect(validatePassEntryName('openpalm/my_key')).toBe('openpalm/my_key');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('validatePassEntryName strips leading/trailing slashes', () => {
|
|
110
|
-
expect(validatePassEntryName('/openpalm/key/')).toBe('openpalm/key');
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('plaintext backend (via detectSecretBackend)', () => {
|
|
115
|
-
test('remove clears value for non-core secrets', async () => {
|
|
116
|
-
const state = createState();
|
|
117
|
-
ensureSecrets(state);
|
|
118
|
-
const backend = detectSecretBackend(state);
|
|
119
|
-
|
|
120
|
-
await backend.write('openpalm/custom/temp', 'temp-value');
|
|
121
|
-
expect(await backend.exists('openpalm/custom/temp')).toBe(true);
|
|
122
|
-
|
|
123
|
-
await backend.remove('openpalm/custom/temp');
|
|
124
|
-
expect(await backend.exists('openpalm/custom/temp')).toBe(false);
|
|
125
|
-
|
|
126
|
-
// Value is cleared — entry shows present: false
|
|
127
|
-
const entries = await backend.list('openpalm/custom/');
|
|
128
|
-
const found = entries.find((e) => e.key === 'openpalm/custom/temp');
|
|
129
|
-
if (found) {
|
|
130
|
-
expect(found.present).toBe(false);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('remove clears value but keeps index for core secrets', async () => {
|
|
135
|
-
const state = createState();
|
|
136
|
-
ensureSecrets(state);
|
|
137
|
-
const backend = detectSecretBackend(state);
|
|
138
|
-
|
|
139
|
-
// Write a core secret
|
|
140
|
-
await backend.write('openpalm/admin-token', 'my-token');
|
|
141
|
-
expect(await backend.exists('openpalm/admin-token')).toBe(true);
|
|
142
|
-
|
|
143
|
-
await backend.remove('openpalm/admin-token');
|
|
144
|
-
expect(await backend.exists('openpalm/admin-token')).toBe(false);
|
|
145
|
-
|
|
146
|
-
// Core secrets still appear in list (as present: false)
|
|
147
|
-
const entries = await backend.list('openpalm/');
|
|
148
|
-
const found = entries.find((e) => e.key === 'openpalm/admin-token');
|
|
149
|
-
expect(found).toBeDefined();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('list includes both core and indexed entries', async () => {
|
|
153
|
-
const state = createState();
|
|
154
|
-
ensureSecrets(state);
|
|
155
|
-
const backend = detectSecretBackend(state);
|
|
156
|
-
|
|
157
|
-
await backend.write('openpalm/custom/my-key', 'value');
|
|
158
|
-
|
|
159
|
-
const entries = await backend.list();
|
|
160
|
-
const coreKeys = entries.filter((e) => e.kind === 'core');
|
|
161
|
-
const customKeys = entries.filter((e) => e.kind === 'custom');
|
|
162
|
-
|
|
163
|
-
expect(coreKeys.length).toBeGreaterThan(0);
|
|
164
|
-
expect(customKeys.length).toBeGreaterThan(0);
|
|
165
|
-
expect(customKeys.find((e) => e.key === 'openpalm/custom/my-key')).toBeDefined();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('generate creates a secret with random value', async () => {
|
|
169
|
-
const state = createState();
|
|
170
|
-
ensureSecrets(state);
|
|
171
|
-
const backend = detectSecretBackend(state);
|
|
172
|
-
|
|
173
|
-
const entry = await backend.generate('openpalm/custom/generated', 64);
|
|
174
|
-
expect(entry.present).toBe(true);
|
|
175
|
-
expect(await backend.exists('openpalm/custom/generated')).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test('user-scope reads from akm vault, system-scope reads from stack.env', async () => {
|
|
179
|
-
// Regression test: user scope must consult the akm vault file, system scope
|
|
180
|
-
// must consult state/stack.env. When both files define the same key with
|
|
181
|
-
// different values, the two scopes must return their own file's value.
|
|
182
|
-
const state = createState();
|
|
183
|
-
ensureSecrets(state);
|
|
184
|
-
const backend = detectSecretBackend(state);
|
|
185
|
-
|
|
186
|
-
// Seed the akm vault file with a user-scope value.
|
|
187
|
-
const akmPath = akmUserVaultPathSync(state);
|
|
188
|
-
mkdirSync(dirname(akmPath), { recursive: true });
|
|
189
|
-
writeFileSync(akmPath, 'OPENAI_API_KEY=akm-vault-openai\n');
|
|
190
|
-
|
|
191
|
-
// Stack.env already exists from ensureSecrets — seed a system token.
|
|
192
|
-
const stackEnvPath = join(state.stackDir, "stack.env");
|
|
193
|
-
const stackContent = readFileSync(stackEnvPath, 'utf-8')
|
|
194
|
-
.replace(/^OP_UI_TOKEN=.*$/m, 'OP_UI_TOKEN=stack-admin-token');
|
|
195
|
-
writeFileSync(stackEnvPath, stackContent);
|
|
196
|
-
|
|
197
|
-
// System scope reads stack.env exclusively.
|
|
198
|
-
expect(await backend.exists('openpalm/admin-token')).toBe(true);
|
|
199
|
-
const systemEntries = await backend.list('openpalm/admin-token');
|
|
200
|
-
expect(systemEntries.find((e) => e.key === 'openpalm/admin-token')?.present).toBe(true);
|
|
201
|
-
|
|
202
|
-
// User scope reads akm vault file.
|
|
203
|
-
const userEntries = await backend.list('openpalm/openai/');
|
|
204
|
-
const openai = userEntries.find((e) => e.key === 'openpalm/openai/api-key');
|
|
205
|
-
expect(openai).toBeDefined();
|
|
206
|
-
expect(openai?.scope).toBe('user');
|
|
207
|
-
expect(openai?.present).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('list/exists resolve user-scope secrets from akm vault', async () => {
|
|
211
|
-
// The backend MUST resolve user-managed secrets through the akm vault file
|
|
212
|
-
// (stash/vaults/user.env), not from any legacy path.
|
|
213
|
-
const state = createState();
|
|
214
|
-
ensureSecrets(state);
|
|
215
|
-
const backend = detectSecretBackend(state);
|
|
216
|
-
|
|
217
|
-
// Place the secret in the akm vault file.
|
|
218
|
-
const akmPath = akmUserVaultPathSync(state);
|
|
219
|
-
mkdirSync(dirname(akmPath), { recursive: true });
|
|
220
|
-
writeFileSync(akmPath, 'OPENAI_API_KEY=migrated-akm-value\n');
|
|
221
|
-
|
|
222
|
-
// exists() must report the user-scope secret as present.
|
|
223
|
-
expect(await backend.exists('openpalm/openai/api-key')).toBe(true);
|
|
224
|
-
|
|
225
|
-
// list() must enumerate it with present: true.
|
|
226
|
-
const entries = await backend.list('openpalm/openai/');
|
|
227
|
-
const openai = entries.find((e) => e.key === 'openpalm/openai/api-key');
|
|
228
|
-
expect(openai).toBeDefined();
|
|
229
|
-
expect(openai?.scope).toBe('user');
|
|
230
|
-
expect(openai?.present).toBe(true);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe('pass backend (via detectSecretBackend)', () => {
|
|
235
|
-
test('reports pass provider when configured', () => {
|
|
236
|
-
const state = createState();
|
|
237
|
-
writeSecretProviderConfig(state, {
|
|
238
|
-
provider: 'pass',
|
|
239
|
-
passwordStoreDir: '/tmp/test-pass-store',
|
|
240
|
-
passPrefix: 'myprefix',
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const backend = detectSecretBackend(state);
|
|
244
|
-
expect(backend.provider).toBe('pass');
|
|
245
|
-
expect(backend.capabilities.generate).toBe(true);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
test('uses default store dir when no config', () => {
|
|
249
|
-
const state = createState();
|
|
250
|
-
writeSecretProviderConfig(state, { provider: 'pass' });
|
|
251
|
-
const backend = detectSecretBackend(state);
|
|
252
|
-
expect(backend.provider).toBe('pass');
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test('exists returns false for non-existent entries', async () => {
|
|
256
|
-
const state = createState();
|
|
257
|
-
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
258
|
-
mkdirSync(storeDir, { recursive: true });
|
|
259
|
-
writeSecretProviderConfig(state, { provider: 'pass', passwordStoreDir: storeDir });
|
|
260
|
-
|
|
261
|
-
const backend = detectSecretBackend(state);
|
|
262
|
-
expect(await backend.exists('openpalm/nonexistent')).toBe(false);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
test('list returns empty array for empty store', async () => {
|
|
266
|
-
const state = createState();
|
|
267
|
-
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
268
|
-
mkdirSync(storeDir, { recursive: true });
|
|
269
|
-
writeSecretProviderConfig(state, { provider: 'pass', passwordStoreDir: storeDir });
|
|
270
|
-
|
|
271
|
-
const backend = detectSecretBackend(state);
|
|
272
|
-
const entries = await backend.list();
|
|
273
|
-
expect(entries).toEqual([]);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test('list scopes to passPrefix subdirectory', async () => {
|
|
277
|
-
const state = createState();
|
|
278
|
-
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
279
|
-
|
|
280
|
-
// Create fake .gpg files under the prefix subdirectory
|
|
281
|
-
const prefixDir = join(storeDir, 'myprefix', 'openpalm');
|
|
282
|
-
mkdirSync(prefixDir, { recursive: true });
|
|
283
|
-
writeFileSync(join(prefixDir, 'admin-token.gpg'), 'fake-gpg-data');
|
|
284
|
-
writeFileSync(join(prefixDir, 'assistant-token.gpg'), 'fake-gpg-data');
|
|
285
|
-
|
|
286
|
-
// Create a file outside the prefix (should not appear)
|
|
287
|
-
mkdirSync(join(storeDir, 'other'), { recursive: true });
|
|
288
|
-
writeFileSync(join(storeDir, 'other', 'secret.gpg'), 'fake');
|
|
289
|
-
|
|
290
|
-
writeSecretProviderConfig(state, {
|
|
291
|
-
provider: 'pass',
|
|
292
|
-
passwordStoreDir: storeDir,
|
|
293
|
-
passPrefix: 'myprefix',
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const backend = detectSecretBackend(state);
|
|
297
|
-
const entries = await backend.list();
|
|
298
|
-
|
|
299
|
-
expect(entries).toHaveLength(2);
|
|
300
|
-
// Keys should be canonical (without prefix)
|
|
301
|
-
expect(entries[0]?.key).toBe('openpalm/admin-token');
|
|
302
|
-
expect(entries[1]?.key).toBe('openpalm/assistant-token');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
test('exists checks prefixed path in store', async () => {
|
|
306
|
-
const state = createState();
|
|
307
|
-
const storeDir = join(rootDir, 'state', 'secrets', 'pass-store');
|
|
308
|
-
const prefixDir = join(storeDir, 'myprefix');
|
|
309
|
-
mkdirSync(join(prefixDir, 'openpalm'), { recursive: true });
|
|
310
|
-
writeFileSync(join(prefixDir, 'openpalm', 'admin-token.gpg'), 'fake');
|
|
311
|
-
|
|
312
|
-
writeSecretProviderConfig(state, {
|
|
313
|
-
provider: 'pass',
|
|
314
|
-
passwordStoreDir: storeDir,
|
|
315
|
-
passPrefix: 'myprefix',
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const backend = detectSecretBackend(state);
|
|
319
|
-
expect(await backend.exists('openpalm/admin-token')).toBe(true);
|
|
320
|
-
expect(await backend.exists('openpalm/nonexistent')).toBe(false);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
describe('detectSecretBackend', () => {
|
|
325
|
-
test('returns plaintext provider by default', () => {
|
|
326
|
-
const state = createState();
|
|
327
|
-
const backend = detectSecretBackend(state);
|
|
328
|
-
expect(backend.provider).toBe('plaintext');
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
test('returns pass provider when provider.json has provider: pass', () => {
|
|
332
|
-
const state = createState();
|
|
333
|
-
writeSecretProviderConfig(state, {
|
|
334
|
-
provider: 'pass',
|
|
335
|
-
passwordStoreDir: '/tmp/test',
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
const backend = detectSecretBackend(state);
|
|
339
|
-
expect(backend.provider).toBe('pass');
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test('returns plaintext provider when provider.json has provider: plaintext', () => {
|
|
343
|
-
const state = createState();
|
|
344
|
-
writeSecretProviderConfig(state, { provider: 'plaintext' });
|
|
345
|
-
|
|
346
|
-
const backend = detectSecretBackend(state);
|
|
347
|
-
expect(backend.provider).toBe('plaintext');
|
|
348
|
-
});
|
|
349
|
-
});
|