env-secrets 0.3.3 → 0.5.0

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.
@@ -0,0 +1,217 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { PassThrough } from 'node:stream';
5
+
6
+ import {
7
+ asOutputFormat,
8
+ parseEnvSecrets,
9
+ parseRecoveryDays,
10
+ printData,
11
+ readStdin,
12
+ renderTable,
13
+ resolveAwsScope,
14
+ resolveSecretValue
15
+ } from '../../src/cli/helpers';
16
+
17
+ describe('cli/helpers', () => {
18
+ it('validates output format', () => {
19
+ expect(asOutputFormat('json')).toBe('json');
20
+ expect(asOutputFormat('table')).toBe('table');
21
+ expect(() => asOutputFormat('xml')).toThrow('Invalid output format');
22
+ });
23
+
24
+ it('renders empty table message', () => {
25
+ const output = renderTable([{ key: 'name', label: 'Name' }], []);
26
+ expect(output).toBe('No results.');
27
+ });
28
+
29
+ it('renders aligned table output', () => {
30
+ const output = renderTable(
31
+ [
32
+ { key: 'name', label: 'Name' },
33
+ { key: 'value', label: 'Value' }
34
+ ],
35
+ [{ name: 'one', value: '1' }]
36
+ );
37
+
38
+ expect(output).toContain('Name');
39
+ expect(output).toContain('Value');
40
+ expect(output).toContain('one');
41
+ });
42
+
43
+ it('prints JSON output', () => {
44
+ const consoleSpy = jest
45
+ .spyOn(console, 'log')
46
+ .mockImplementation(() => undefined);
47
+
48
+ printData('json', [{ key: 'name', label: 'Name' }], [{ name: 'value' }]);
49
+
50
+ expect(consoleSpy).toHaveBeenCalledWith(
51
+ JSON.stringify([{ name: 'value' }], null, 2)
52
+ );
53
+ consoleSpy.mockRestore();
54
+ });
55
+
56
+ it('parses valid recovery days and rejects invalid values', () => {
57
+ expect(parseRecoveryDays('7')).toBe(7);
58
+ expect(parseRecoveryDays('30')).toBe(30);
59
+ expect(() => parseRecoveryDays('6')).toThrow();
60
+ expect(() => parseRecoveryDays('31')).toThrow();
61
+ expect(() => parseRecoveryDays('abc')).toThrow();
62
+ });
63
+
64
+ it('reads stdin content and strips one trailing newline', async () => {
65
+ const stream = new PassThrough();
66
+ const promise = readStdin(stream as unknown as NodeJS.ReadStream);
67
+
68
+ stream.write('secret-value\n');
69
+ stream.end();
70
+
71
+ await expect(promise).resolves.toBe('secret-value');
72
+ });
73
+
74
+ it('resolves secret value from explicit --value', async () => {
75
+ await expect(resolveSecretValue('inline', false)).resolves.toBe('inline');
76
+ });
77
+
78
+ it('rejects when both --value and --value-stdin are used', async () => {
79
+ await expect(resolveSecretValue('inline', true)).rejects.toThrow(
80
+ 'Use only one secret value source: --value, --value-stdin, or --file.'
81
+ );
82
+ });
83
+
84
+ it('rejects when --file and --value-stdin are used together', async () => {
85
+ await expect(
86
+ resolveSecretValue(undefined, true, './secret.txt')
87
+ ).rejects.toThrow(
88
+ 'Use only one secret value source: --value, --value-stdin, or --file.'
89
+ );
90
+ });
91
+
92
+ it('rejects when --value and --file are used together', async () => {
93
+ await expect(
94
+ resolveSecretValue('inline', false, './secret.txt')
95
+ ).rejects.toThrow(
96
+ 'Use only one secret value source: --value, --value-stdin, or --file.'
97
+ );
98
+ });
99
+
100
+ it('treats explicit empty --value as provided for mutual exclusion', async () => {
101
+ await expect(resolveSecretValue('', false, './secret.txt')).rejects.toThrow(
102
+ 'Use only one secret value source: --value, --value-stdin, or --file.'
103
+ );
104
+ });
105
+
106
+ it('rejects stdin mode when no stdin is provided', async () => {
107
+ const originalStdin = process.stdin;
108
+ Object.defineProperty(process, 'stdin', {
109
+ value: { ...originalStdin, isTTY: true },
110
+ configurable: true
111
+ });
112
+
113
+ await expect(resolveSecretValue(undefined, true)).rejects.toThrow(
114
+ 'No stdin detected. Pipe a value when using --value-stdin.'
115
+ );
116
+
117
+ Object.defineProperty(process, 'stdin', {
118
+ value: originalStdin,
119
+ configurable: true
120
+ });
121
+ });
122
+
123
+ it('reads secret value from file and strips one trailing newline', async () => {
124
+ const dir = mkdtempSync(join(tmpdir(), 'env-secrets-test-'));
125
+ const file = join(dir, 'secret.txt');
126
+ writeFileSync(file, 'file-secret\n');
127
+
128
+ await expect(resolveSecretValue(undefined, false, file)).resolves.toBe(
129
+ 'file-secret'
130
+ );
131
+
132
+ rmSync(dir, { recursive: true, force: true });
133
+ });
134
+
135
+ it('parses env secrets from KEY=value and export KEY=value formats', () => {
136
+ const parsed = parseEnvSecrets(
137
+ [
138
+ '# comment',
139
+ 'export API_KEY=secret1',
140
+ 'DATABASE_URL=postgres://db',
141
+ ''
142
+ ].join('\n')
143
+ );
144
+
145
+ expect(parsed.entries).toEqual([
146
+ { key: 'API_KEY', value: 'secret1', line: 2 },
147
+ { key: 'DATABASE_URL', value: 'postgres://db', line: 3 }
148
+ ]);
149
+ expect(parsed.skipped).toEqual([]);
150
+ });
151
+
152
+ it('handles whitespace around equals in env file', () => {
153
+ const parsed = parseEnvSecrets(' export NAME = secret1 ');
154
+
155
+ expect(parsed.entries).toEqual([
156
+ { key: 'NAME', value: 'secret1', line: 1 }
157
+ ]);
158
+ });
159
+
160
+ it('skips duplicate keys when parsing env file', () => {
161
+ const parsed = parseEnvSecrets(['A=1', 'A=2'].join('\n'));
162
+
163
+ expect(parsed.entries).toEqual([{ key: 'A', value: '1', line: 1 }]);
164
+ expect(parsed.skipped).toEqual([
165
+ { key: 'A', line: 2, reason: 'duplicate key' }
166
+ ]);
167
+ });
168
+
169
+ it('throws clear error for malformed env lines', () => {
170
+ expect(() => parseEnvSecrets('NOT_A_VALID_LINE')).toThrow(
171
+ 'Malformed env line 1'
172
+ );
173
+ expect(() => parseEnvSecrets('BAD=secret')).not.toThrow();
174
+ });
175
+
176
+ it('does not include raw line content in malformed env errors', () => {
177
+ expect(() => parseEnvSecrets('export SECRET_ONLY')).toThrow(
178
+ 'Expected KEY=value or export KEY=value.'
179
+ );
180
+ expect(() => parseEnvSecrets('export SECRET_ONLY')).not.toThrow(
181
+ /SECRET_ONLY/
182
+ );
183
+ });
184
+
185
+ it('prefers explicit aws scope options over global options', () => {
186
+ const command = {
187
+ optsWithGlobals: () => ({
188
+ profile: 'global-profile',
189
+ region: 'us-west-2'
190
+ })
191
+ };
192
+
193
+ expect(
194
+ resolveAwsScope(
195
+ { profile: 'local-profile', region: 'us-east-1' },
196
+ command
197
+ )
198
+ ).toEqual({
199
+ profile: 'local-profile',
200
+ region: 'us-east-1'
201
+ });
202
+ });
203
+
204
+ it('falls back to global aws scope options when local options are absent', () => {
205
+ const command = {
206
+ optsWithGlobals: () => ({
207
+ profile: 'global-profile',
208
+ region: 'us-west-2'
209
+ })
210
+ };
211
+
212
+ expect(resolveAwsScope({}, command)).toEqual({
213
+ profile: 'global-profile',
214
+ region: 'us-west-2'
215
+ });
216
+ });
217
+ });
@@ -0,0 +1,85 @@
1
+ import { fromIni } from '@aws-sdk/credential-providers';
2
+
3
+ jest.mock('@aws-sdk/credential-providers');
4
+
5
+ import { buildAwsClientConfig } from '../../src/vaults/aws-config';
6
+
7
+ const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
8
+
9
+ describe('aws-config', () => {
10
+ let originalEnv: NodeJS.ProcessEnv;
11
+
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ originalEnv = { ...process.env };
15
+ process.env = { ...originalEnv };
16
+ });
17
+
18
+ afterEach(() => {
19
+ process.env = originalEnv;
20
+ });
21
+
22
+ it('uses explicit profile when provided', () => {
23
+ const mockCredentials = jest.fn();
24
+ mockFromIni.mockReturnValue(mockCredentials as ReturnType<typeof fromIni>);
25
+
26
+ const config = buildAwsClientConfig({
27
+ profile: 'my-profile',
28
+ region: 'us-east-1'
29
+ });
30
+
31
+ expect(mockFromIni).toHaveBeenCalledWith({ profile: 'my-profile' });
32
+ expect(config).toEqual({
33
+ region: 'us-east-1',
34
+ credentials: mockCredentials
35
+ });
36
+ });
37
+
38
+ it('uses environment credentials when access key and secret are present', () => {
39
+ process.env.AWS_ACCESS_KEY_ID = 'test';
40
+ process.env.AWS_SECRET_ACCESS_KEY = 'test';
41
+
42
+ const config = buildAwsClientConfig({ region: 'us-east-1' });
43
+
44
+ expect(mockFromIni).not.toHaveBeenCalled();
45
+ expect(config).toEqual({
46
+ region: 'us-east-1',
47
+ credentials: undefined
48
+ });
49
+ });
50
+
51
+ it('falls back to default profile when no explicit credentials are provided', () => {
52
+ const mockCredentials = jest.fn();
53
+ mockFromIni.mockReturnValue(mockCredentials as ReturnType<typeof fromIni>);
54
+
55
+ const config = buildAwsClientConfig({ region: 'us-east-1' });
56
+
57
+ expect(mockFromIni).toHaveBeenCalledWith({ profile: 'default' });
58
+ expect(config).toEqual({
59
+ region: 'us-east-1',
60
+ credentials: mockCredentials
61
+ });
62
+ });
63
+
64
+ it('prefers AWS_ENDPOINT_URL for custom endpoint', () => {
65
+ const mockCredentials = jest.fn();
66
+ mockFromIni.mockReturnValue(mockCredentials as ReturnType<typeof fromIni>);
67
+ process.env.AWS_ENDPOINT_URL = 'http://localhost:4566';
68
+ process.env.AWS_SECRETS_MANAGER_ENDPOINT = 'http://localhost:4577';
69
+
70
+ const config = buildAwsClientConfig({ region: 'us-east-1' });
71
+
72
+ expect(config.endpoint).toBe('http://localhost:4566');
73
+ });
74
+
75
+ it('uses AWS_SECRETS_MANAGER_ENDPOINT when AWS_ENDPOINT_URL is unset', () => {
76
+ const mockCredentials = jest.fn();
77
+ mockFromIni.mockReturnValue(mockCredentials as ReturnType<typeof fromIni>);
78
+ delete process.env.AWS_ENDPOINT_URL;
79
+ process.env.AWS_SECRETS_MANAGER_ENDPOINT = 'http://localhost:4577';
80
+
81
+ const config = buildAwsClientConfig({ region: 'us-east-1' });
82
+
83
+ expect(config.endpoint).toBe('http://localhost:4577');
84
+ });
85
+ });
@@ -0,0 +1,355 @@
1
+ import { SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
2
+ import { STSClient } from '@aws-sdk/client-sts';
3
+ import { fromIni } from '@aws-sdk/credential-providers';
4
+
5
+ const mockSecretsManagerSend = jest.fn();
6
+ const mockSTSSend = jest.fn();
7
+
8
+ jest.mock('@aws-sdk/client-secrets-manager', () => {
9
+ return {
10
+ SecretsManagerClient: jest.fn().mockImplementation(() => ({
11
+ send: mockSecretsManagerSend
12
+ })),
13
+ CreateSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
14
+ UpdateSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
15
+ GetSecretValueCommand: jest.fn().mockImplementation((input) => ({ input })),
16
+ ListSecretsCommand: jest.fn().mockImplementation((input) => ({ input })),
17
+ DescribeSecretCommand: jest.fn().mockImplementation((input) => ({ input })),
18
+ DeleteSecretCommand: jest.fn().mockImplementation((input) => ({ input }))
19
+ };
20
+ });
21
+
22
+ jest.mock('@aws-sdk/client-sts', () => {
23
+ return {
24
+ STSClient: jest.fn().mockImplementation(() => ({
25
+ send: mockSTSSend
26
+ })),
27
+ GetCallerIdentityCommand: jest
28
+ .fn()
29
+ .mockImplementation((input) => ({ input }))
30
+ };
31
+ });
32
+
33
+ jest.mock('@aws-sdk/credential-providers');
34
+
35
+ import {
36
+ createSecret,
37
+ updateSecret,
38
+ listSecrets,
39
+ getSecretMetadata,
40
+ deleteSecret,
41
+ secretExists,
42
+ getSecretString,
43
+ validateSecretName
44
+ } from '../../src/vaults/secretsmanager-admin';
45
+
46
+ const mockSecretsManagerClient = SecretsManagerClient as jest.MockedClass<
47
+ typeof SecretsManagerClient
48
+ >;
49
+ const mockSTSClient = STSClient as jest.MockedClass<typeof STSClient>;
50
+ const mockFromIni = fromIni as jest.MockedFunction<typeof fromIni>;
51
+
52
+ describe('secretsmanager-admin', () => {
53
+ let originalEnv: NodeJS.ProcessEnv;
54
+
55
+ beforeEach(() => {
56
+ jest.clearAllMocks();
57
+ originalEnv = { ...process.env };
58
+ process.env = { ...originalEnv };
59
+ mockSTSSend.mockResolvedValue({ Account: '123456789012' });
60
+ });
61
+
62
+ afterEach(() => {
63
+ process.env = originalEnv;
64
+ });
65
+
66
+ it('validates secret names', () => {
67
+ expect(() => validateSecretName('valid/name_1')).not.toThrow();
68
+ expect(() => validateSecretName('invalid name')).toThrow(
69
+ 'Invalid secret name'
70
+ );
71
+ });
72
+
73
+ it('creates a secret with tags', async () => {
74
+ mockSecretsManagerSend.mockResolvedValueOnce({
75
+ Name: 'test-secret',
76
+ ARN: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret',
77
+ VersionId: 'v1'
78
+ });
79
+
80
+ const result = await createSecret({
81
+ name: 'test-secret',
82
+ value: '{"API_KEY":"abc"}',
83
+ description: 'test',
84
+ tags: ['team=platform', 'env=dev'],
85
+ region: 'us-east-1'
86
+ });
87
+
88
+ expect(mockSTSClient).toHaveBeenCalled();
89
+ expect(mockSecretsManagerClient).toHaveBeenCalled();
90
+ expect(mockSecretsManagerSend).toHaveBeenCalledWith(
91
+ expect.objectContaining({
92
+ input: expect.objectContaining({
93
+ Name: 'test-secret',
94
+ SecretString: '{"API_KEY":"abc"}',
95
+ Tags: [
96
+ { Key: 'team', Value: 'platform' },
97
+ { Key: 'env', Value: 'dev' }
98
+ ]
99
+ })
100
+ })
101
+ );
102
+ expect(result).toEqual({
103
+ arn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret',
104
+ name: 'test-secret',
105
+ versionId: 'v1'
106
+ });
107
+ });
108
+
109
+ it('supports tags with multiple equals signs', async () => {
110
+ mockSecretsManagerSend.mockResolvedValueOnce({
111
+ Name: 'test-secret',
112
+ ARN: 'arn:test',
113
+ VersionId: 'v1'
114
+ });
115
+
116
+ await createSecret({
117
+ name: 'test-secret',
118
+ value: 'value',
119
+ tags: ['url=https://example.com?a=1'],
120
+ region: 'us-east-1'
121
+ });
122
+
123
+ expect(mockSecretsManagerSend).toHaveBeenCalledWith(
124
+ expect.objectContaining({
125
+ input: expect.objectContaining({
126
+ Tags: [{ Key: 'url', Value: 'https://example.com?a=1' }]
127
+ })
128
+ })
129
+ );
130
+ });
131
+
132
+ it('throws for malformed tags', async () => {
133
+ await expect(
134
+ createSecret({
135
+ name: 'test-secret',
136
+ value: 'value',
137
+ tags: ['invalid-tag'],
138
+ region: 'us-east-1'
139
+ })
140
+ ).rejects.toThrow('Invalid tag format');
141
+ });
142
+
143
+ it('omits tags when none are provided', async () => {
144
+ mockSecretsManagerSend.mockResolvedValueOnce({
145
+ Name: 'test-secret',
146
+ ARN: 'arn:test',
147
+ VersionId: 'v1'
148
+ });
149
+
150
+ await createSecret({
151
+ name: 'test-secret',
152
+ value: 'value',
153
+ region: 'us-east-1'
154
+ });
155
+
156
+ expect(mockSecretsManagerSend).toHaveBeenCalledWith(
157
+ expect.objectContaining({
158
+ input: expect.objectContaining({
159
+ Tags: undefined
160
+ })
161
+ })
162
+ );
163
+ });
164
+
165
+ it('maps create secret AlreadyExistsException', async () => {
166
+ mockSecretsManagerSend.mockRejectedValueOnce({
167
+ name: 'AlreadyExistsException'
168
+ });
169
+
170
+ await expect(
171
+ createSecret({
172
+ name: 'existing-secret',
173
+ value: 'abc',
174
+ region: 'us-east-1'
175
+ })
176
+ ).rejects.toThrow('already exists');
177
+ });
178
+
179
+ it('updates a secret value and description', async () => {
180
+ mockSecretsManagerSend.mockResolvedValueOnce({
181
+ Name: 'test-secret',
182
+ ARN: 'arn:test',
183
+ VersionId: 'v2'
184
+ });
185
+
186
+ const result = await updateSecret({
187
+ name: 'test-secret',
188
+ value: '{"API_KEY":"new"}',
189
+ description: 'updated',
190
+ region: 'us-east-1'
191
+ });
192
+
193
+ expect(mockSecretsManagerSend).toHaveBeenCalledWith(
194
+ expect.objectContaining({
195
+ input: expect.objectContaining({
196
+ SecretId: 'test-secret',
197
+ SecretString: '{"API_KEY":"new"}',
198
+ Description: 'updated'
199
+ })
200
+ })
201
+ );
202
+ expect(result.versionId).toBe('v2');
203
+ });
204
+
205
+ it('lists secrets and applies prefix/tag filters', async () => {
206
+ mockSecretsManagerSend.mockResolvedValueOnce({
207
+ SecretList: [
208
+ {
209
+ Name: 'app/one',
210
+ Description: 'One',
211
+ Tags: [{ Key: 'env', Value: 'dev' }],
212
+ LastChangedDate: new Date('2026-02-16T00:00:00.000Z')
213
+ },
214
+ {
215
+ Name: 'app/two',
216
+ Description: 'Two',
217
+ Tags: [{ Key: 'env', Value: 'prod' }],
218
+ LastChangedDate: new Date('2026-02-16T00:00:00.000Z')
219
+ }
220
+ ]
221
+ });
222
+
223
+ const result = await listSecrets({
224
+ prefix: 'app/',
225
+ tags: ['env=dev'],
226
+ region: 'us-east-1'
227
+ });
228
+
229
+ expect(result).toHaveLength(1);
230
+ expect(result[0].name).toBe('app/one');
231
+ });
232
+
233
+ it('ignores invalid AWS tags during tag filtering', async () => {
234
+ mockSecretsManagerSend.mockResolvedValueOnce({
235
+ SecretList: [
236
+ {
237
+ Name: 'app/one',
238
+ Tags: [{ Key: undefined, Value: 'dev' }]
239
+ },
240
+ {
241
+ Name: 'app/two',
242
+ Tags: [{ Key: 'env', Value: 'dev' }]
243
+ }
244
+ ]
245
+ });
246
+
247
+ const result = await listSecrets({
248
+ tags: ['env=dev'],
249
+ region: 'us-east-1'
250
+ });
251
+
252
+ expect(result).toHaveLength(1);
253
+ expect(result[0].name).toBe('app/two');
254
+ });
255
+
256
+ it('returns secret metadata without secret value', async () => {
257
+ mockSecretsManagerSend.mockResolvedValueOnce({
258
+ Name: 'app/meta',
259
+ ARN: 'arn:meta',
260
+ Description: 'Meta secret',
261
+ KmsKeyId: 'kms-key',
262
+ CreatedDate: new Date('2026-02-16T00:00:00.000Z'),
263
+ LastChangedDate: new Date('2026-02-16T01:00:00.000Z'),
264
+ VersionIdsToStages: { abc: ['AWSCURRENT'] },
265
+ Tags: [{ Key: 'env', Value: 'dev' }]
266
+ });
267
+
268
+ const result = await getSecretMetadata({
269
+ name: 'app/meta',
270
+ region: 'us-east-1'
271
+ });
272
+
273
+ expect(result).toEqual(
274
+ expect.objectContaining({
275
+ name: 'app/meta',
276
+ arn: 'arn:meta',
277
+ tags: { env: 'dev' },
278
+ versionIdsToStages: { abc: ['AWSCURRENT'] }
279
+ })
280
+ );
281
+ expect(Object.keys(result)).not.toContain('secretString');
282
+ });
283
+
284
+ it('returns true when secret exists', async () => {
285
+ mockSecretsManagerSend.mockResolvedValueOnce({
286
+ Name: 'app/existing'
287
+ });
288
+
289
+ await expect(
290
+ secretExists({ name: 'app/existing', region: 'us-east-1' })
291
+ ).resolves.toBe(true);
292
+ });
293
+
294
+ it('returns false when secret does not exist', async () => {
295
+ mockSecretsManagerSend.mockRejectedValueOnce({
296
+ name: 'ResourceNotFoundException'
297
+ });
298
+
299
+ await expect(
300
+ secretExists({ name: 'app/missing', region: 'us-east-1' })
301
+ ).resolves.toBe(false);
302
+ });
303
+
304
+ it('gets current secret string value', async () => {
305
+ mockSecretsManagerSend.mockResolvedValueOnce({
306
+ SecretString: '{"API_KEY":"abc"}'
307
+ });
308
+
309
+ await expect(
310
+ getSecretString({ name: 'app/existing', region: 'us-east-1' })
311
+ ).resolves.toBe('{"API_KEY":"abc"}');
312
+ });
313
+
314
+ it('rejects binary/non-string secrets for append/remove workflows', async () => {
315
+ mockSecretsManagerSend.mockResolvedValueOnce({
316
+ SecretBinary: new Uint8Array([1, 2, 3])
317
+ });
318
+
319
+ await expect(
320
+ getSecretString({ name: 'app/existing', region: 'us-east-1' })
321
+ ).rejects.toThrow('cannot be edited with append/remove');
322
+ });
323
+
324
+ it('deletes secret with recovery options', async () => {
325
+ const mockCredentials = jest.fn().mockResolvedValue({
326
+ accessKeyId: 'default',
327
+ secretAccessKey: 'default'
328
+ });
329
+ mockFromIni.mockReturnValue(mockCredentials);
330
+
331
+ mockSecretsManagerSend.mockResolvedValueOnce({
332
+ Name: 'app/delete',
333
+ ARN: 'arn:delete',
334
+ DeletionDate: new Date('2026-02-16T02:00:00.000Z')
335
+ });
336
+
337
+ const result = await deleteSecret({
338
+ name: 'app/delete',
339
+ recoveryDays: 7,
340
+ profile: 'test-profile',
341
+ region: 'us-east-1'
342
+ });
343
+
344
+ expect(mockFromIni).toHaveBeenCalledWith({ profile: 'test-profile' });
345
+ expect(mockSecretsManagerSend).toHaveBeenCalledWith(
346
+ expect.objectContaining({
347
+ input: expect.objectContaining({
348
+ SecretId: 'app/delete',
349
+ RecoveryWindowInDays: 7
350
+ })
351
+ })
352
+ );
353
+ expect(result.name).toBe('app/delete');
354
+ });
355
+ });