From 8a59d6ef334994d85bfcfb0ee0d212296253da71 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Mon, 14 Oct 2024 16:01:30 +1100 Subject: [PATCH] feat: changed parsers to allow using vault name without secret path chore: vault commands are now using vaultNameParser chore: writing fastcheck tests for parsers chore: jestified outputFormatter tests feat: updated tests for every secret command to allow undefined secret path fix: consistently using command instead of [...command] in secret tests chore: stream consumption must be inside retryAuthentication chore: tests consistently use command fix: lint fix: mkdir fix: variable name --- src/errors.ts | 18 ++ src/secrets/CommandCat.ts | 50 ++-- src/secrets/CommandCreate.ts | 2 +- src/secrets/CommandEdit.ts | 4 +- src/secrets/CommandList.ts | 8 +- src/secrets/CommandMkdir.ts | 64 ++--- src/secrets/CommandRemove.ts | 50 ++-- src/secrets/CommandRename.ts | 9 +- src/secrets/CommandStat.ts | 4 +- src/secrets/CommandWrite.ts | 4 +- src/utils/options.ts | 8 +- src/utils/parsers.ts | 104 +++----- src/vaults/CommandList.ts | 5 +- tests/secrets/cat.test.ts | 24 +- tests/secrets/create.test.ts | 109 +++++---- tests/secrets/edit.test.ts | 93 ++++--- tests/secrets/env.test.ts | 381 ++++++++++++----------------- tests/secrets/list.test.ts | 236 +++++++++--------- tests/secrets/mkdir.test.ts | 30 ++- tests/secrets/newDirSecret.test.ts | 47 ++-- tests/secrets/remove.test.ts | 270 ++++++++++++-------- tests/secrets/rename.test.ts | 30 ++- tests/secrets/stat.test.ts | 47 +++- tests/secrets/write.test.ts | 30 ++- tests/utils.test.ts | 191 +++++++++------ 25 files changed, 990 insertions(+), 828 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index c229393e..bc5a78f9 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -157,6 +157,21 @@ class ErrorPolykeyCLIMakeDirectory extends ErrorPolykeyCLI { exitCode = 1; } +class ErrorPolykeyCLIRenameSecret extends ErrorPolykeyCLI { + static description = 'Failed to rename one or more secrets'; + exitCode = 1; +} + +class ErrorPolykeyCLIRemoveSecret extends ErrorPolykeyCLI { + static description = 'Failed to remove one or more secrets'; + exitCode = 1; +} + +class ErrorPolykeyCLICatSecret extends ErrorPolykeyCLI { + static description = 'Failed to concatenate one or more secrets'; + exitCode = 1; +} + export { ErrorPolykeyCLI, ErrorPolykeyCLIUncaughtException, @@ -178,4 +193,7 @@ export { ErrorPolykeyCLIInvalidEnvName, ErrorPolykeyCLIDuplicateEnvName, ErrorPolykeyCLIMakeDirectory, + ErrorPolykeyCLIRenameSecret, + ErrorPolykeyCLIRemoveSecret, + ErrorPolykeyCLICatSecret, }; diff --git a/src/secrets/CommandCat.ts b/src/secrets/CommandCat.ts index 96b65b7a..dd2e99c4 100644 --- a/src/secrets/CommandCat.ts +++ b/src/secrets/CommandCat.ts @@ -4,6 +4,7 @@ import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binParsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; +import * as errors from '../errors'; class CommandGet extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -21,7 +22,7 @@ class CommandGet extends CommandPolykey { this.addOption(binOptions.clientPort); this.action(async (secretPaths, options) => { secretPaths = secretPaths.map((path: string) => - binParsers.parseSecretPathValue(path), + binParsers.parseSecretPath(path), ); const { default: PolykeyClient } = await import( 'polykey/dist/PolykeyClient' @@ -77,28 +78,39 @@ class CommandGet extends CommandPolykey { }); return; } - await binUtils.retryAuthentication(async (auth) => { + const hasErrored = await binUtils.retryAuthentication(async (auth) => { + // Write secret paths to input stream const response = await pkClient.rpcClient.methods.vaultsSecretsGet(); - await (async () => { - const writer = response.writable.getWriter(); - let first = true; - for (const [vaultName, secretPath] of secretPaths) { - await writer.write({ - nameOrId: vaultName, - secretName: secretPath, - metadata: first - ? { ...auth, options: { continueOnError: true } } - : undefined, - }); - first = false; - } - await writer.close(); - })(); + const writer = response.writable.getWriter(); + let first = true; + for (const [vaultName, secretPath] of secretPaths) { + await writer.write({ + nameOrId: vaultName, + secretName: secretPath ?? '/', + metadata: first + ? { ...auth, options: { continueOnError: true } } + : undefined, + }); + first = false; + } + await writer.close(); + // Print out incoming data to standard out + let hasErrored = false; for await (const chunk of response.readable) { - if (chunk.error) process.stderr.write(chunk.error); - else process.stdout.write(chunk.secretContent); + if (chunk.error) { + hasErrored = true; + process.stderr.write(chunk.error); + } else { + process.stdout.write(chunk.secretContent); + } } + return hasErrored; }, meta); + if (hasErrored) { + throw new errors.ErrorPolykeyCLICatSecret( + 'Failed to concatenate one or more secrets', + ); + } } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/secrets/CommandCreate.ts b/src/secrets/CommandCreate.ts index b98dba2c..3de42a25 100644 --- a/src/secrets/CommandCreate.ts +++ b/src/secrets/CommandCreate.ts @@ -73,7 +73,7 @@ class CommandCreate extends CommandPolykey { pkClient.rpcClient.methods.vaultsSecretsNew({ metadata: auth, nameOrId: secretPath[0], - secretName: secretPath[1], + secretName: secretPath[1] ?? '/', secretContent: content.toString('binary'), }), meta, diff --git a/src/secrets/CommandEdit.ts b/src/secrets/CommandEdit.ts index 7a615d1e..f89269e1 100644 --- a/src/secrets/CommandEdit.ts +++ b/src/secrets/CommandEdit.ts @@ -17,7 +17,7 @@ class CommandEdit extends CommandPolykey { this.argument( '', 'Path to the secret to be edited, specified as :', - binParsers.parseSecretPathValue, + binParsers.parseSecretPath, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); @@ -68,7 +68,7 @@ class CommandEdit extends CommandPolykey { const writer = res.writable.getWriter(); await writer.write({ nameOrId: secretPath[0], - secretName: secretPath[1], + secretName: secretPath[1] ?? '/', metadata: auth, }); await writer.close(); diff --git a/src/secrets/CommandList.ts b/src/secrets/CommandList.ts index ef0c1f8f..07adb691 100644 --- a/src/secrets/CommandList.ts +++ b/src/secrets/CommandList.ts @@ -14,12 +14,12 @@ class CommandList extends CommandPolykey { this.argument( '', 'Directory to list files from, specified as [:]', - binParsers.parseSecretPathOptional, + binParsers.parseSecretPath, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (vaultPattern, options) => { + this.action(async (secretPath, options) => { const { default: PolykeyClient } = await import( 'polykey/dist/PolykeyClient' ); @@ -52,8 +52,8 @@ class CommandList extends CommandPolykey { const secretPaths: Array = []; const stream = await pkClient.rpcClient.methods.vaultsSecretsList({ metadata: auth, - nameOrId: vaultPattern[0], - secretName: vaultPattern[1] ?? '/', + nameOrId: secretPath[0], + secretName: secretPath[1] ?? '/', }); for await (const secret of stream) { // Remove leading slashes diff --git a/src/secrets/CommandMkdir.ts b/src/secrets/CommandMkdir.ts index 4d1ce2da..073383f0 100644 --- a/src/secrets/CommandMkdir.ts +++ b/src/secrets/CommandMkdir.ts @@ -24,7 +24,7 @@ class CommandMkdir extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.addOption(binOptions.recursive); + this.addOption(binOptions.parents); this.action(async (secretPaths, options) => { secretPaths = secretPaths.map((path: string) => binParsers.parseSecretPath(path), @@ -59,7 +59,8 @@ class CommandMkdir extends CommandPolykey { }, logger: this.logger.getChild(PolykeyClient.name), }); - const response = await binUtils.retryAuthentication(async (auth) => { + const hasErrored = await binUtils.retryAuthentication(async (auth) => { + // Write directory paths to input stream const response = await pkClient.rpcClient.methods.vaultsSecretsMkdir(); const writer = response.writable.getWriter(); @@ -67,43 +68,44 @@ class CommandMkdir extends CommandPolykey { for (const [vault, path] of secretPaths) { await writer.write({ nameOrId: vault, - dirName: path, + dirName: path ?? '/', metadata: first - ? { ...auth, options: { recursive: options.recursive } } + ? { ...auth, options: { recursive: options.parents } } : undefined, }); first = false; } await writer.close(); - return response; - }, meta); - - let hasErrored = false; - for await (const result of response.readable) { - if (result.type === 'error') { - // TS cannot properly evaluate a type this deeply nested, so we use - // the as keyword to help it. Inside this block, the type of data is - // ensured to be 'error'. - const error = result as ErrorMessage; - hasErrored = true; - let message: string = ''; - switch (error.code) { - case 'ENOENT': - message = 'No such secret or directory'; - break; - case 'EEXIST': - message = 'Secret or directory exists'; - break; - default: - throw new ErrorPolykeyCLIUncaughtException( - `Unexpected error code: ${error.code}`, - ); + // Print out incoming data to standard out, or incoming errors to + // standard error. + let hasErrored = false; + for await (const result of response.readable) { + if (result.type === 'error') { + // TS cannot properly evaluate a type this deeply nested, so we use + // the as keyword to help it. Inside this block, the type of data + // is ensured to be 'error'. + const error = result as ErrorMessage; + hasErrored = true; + let message: string = ''; + switch (error.code) { + case 'ENOENT': + message = 'No such secret or directory'; + break; + case 'EEXIST': + message = 'Secret or directory exists'; + break; + default: + throw new ErrorPolykeyCLIUncaughtException( + `Unexpected error code: ${error.code}`, + ); + } + process.stderr.write( + `${error.code}: cannot create directory ${error.reason}: ${message}\n`, + ); } - process.stderr.write( - `${error.code}: cannot create directory ${error.reason}: ${message}\n`, - ); } - } + return hasErrored; + }, meta); if (hasErrored) { throw new ErrorPolykeyCLIMakeDirectory( 'Failed to create one or more directories', diff --git a/src/secrets/CommandRemove.ts b/src/secrets/CommandRemove.ts index 9fa527c3..a9dcbd35 100644 --- a/src/secrets/CommandRemove.ts +++ b/src/secrets/CommandRemove.ts @@ -4,6 +4,7 @@ import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binParsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; +import * as errors from '../errors'; class CommandRemove extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -20,9 +21,17 @@ class CommandRemove extends CommandPolykey { this.addOption(binOptions.clientPort); this.addOption(binOptions.recursive); this.action(async (secretPaths, options) => { - secretPaths = secretPaths.map((path: string) => - binParsers.parseSecretPathValue(path), - ); + for (let i = 0; i < secretPaths.length; i++) { + const value: string = secretPaths[i]; + const parsedValue = binParsers.parseSecretPath(value); + // The vault root cannot be deleted + if (parsedValue[1] == null) { + throw new errors.ErrorPolykeyCLIRemoveSecret( + 'EPERM: Cannot remove vault root', + ); + } + secretPaths[i] = parsedValue; + } const { default: PolykeyClient } = await import( 'polykey/dist/PolykeyClient' ); @@ -50,28 +59,25 @@ class CommandRemove extends CommandPolykey { options: { nodePath: options.nodePath }, logger: this.logger.getChild(PolykeyClient.name), }); - const response = await binUtils.retryAuthentication(async (auth) => { + await binUtils.retryAuthentication(async (auth) => { const response = await pkClient.rpcClient.methods.vaultsSecretsRemove(); - await (async () => { - const writer = response.writable.getWriter(); - let first = true; - for (const [vault, path] of secretPaths) { - await writer.write({ - nameOrId: vault, - secretName: path, - metadata: first - ? { ...auth, options: { recursive: options.recursive } } - : undefined, - }); - first = false; - } - await writer.close(); - })(); - return response; + const writer = response.writable.getWriter(); + let first = true; + for (const [vault, path] of secretPaths) { + await writer.write({ + nameOrId: vault, + secretName: path, + metadata: first + ? { ...auth, options: { recursive: options.recursive } } + : undefined, + }); + first = false; + } + await writer.close(); + // Wait for the program to generate a response (complete processing). + await response.output; }, meta); - // Wait for the program to generate a response (complete processing). - await response.output; } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/secrets/CommandRename.ts b/src/secrets/CommandRename.ts index 494be642..7582c345 100644 --- a/src/secrets/CommandRename.ts +++ b/src/secrets/CommandRename.ts @@ -4,6 +4,7 @@ import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binParsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; +import * as errors from '../errors'; class CommandRename extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -13,13 +14,19 @@ class CommandRename extends CommandPolykey { this.argument( '', 'Path to where the secret to be renamed, specified as :', - binParsers.parseSecretPathValue, + binParsers.parseSecretPath, ); this.argument('', 'New name of the secret'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); this.action(async (secretPath, newSecretName, options) => { + // Ensure that a valid secret path is provided + if (secretPath[1] == null) { + throw new errors.ErrorPolykeyCLIRenameSecret( + 'EPERM: Cannot rename vault root', + ); + } const { default: PolykeyClient } = await import( 'polykey/dist/PolykeyClient' ); diff --git a/src/secrets/CommandStat.ts b/src/secrets/CommandStat.ts index 26633aee..dea14582 100644 --- a/src/secrets/CommandStat.ts +++ b/src/secrets/CommandStat.ts @@ -13,7 +13,7 @@ class CommandStat extends CommandPolykey { this.argument( '', 'Path to where the secret, specified as :', - binParsers.parseSecretPathValue, + binParsers.parseSecretPath, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); @@ -55,7 +55,7 @@ class CommandStat extends CommandPolykey { pkClient.rpcClient.methods.vaultsSecretsStat({ metadata: auth, nameOrId: secretPath[0], - secretName: secretPath[1], + secretName: secretPath[1] ?? '/', }), meta, ); diff --git a/src/secrets/CommandWrite.ts b/src/secrets/CommandWrite.ts index 76bbad22..9543f726 100644 --- a/src/secrets/CommandWrite.ts +++ b/src/secrets/CommandWrite.ts @@ -13,7 +13,7 @@ class CommandWrite extends CommandPolykey { this.argument( '', 'Path to the secret, specified as :', - binParsers.parseSecretPathValue, + binParsers.parseSecretPath, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); @@ -77,7 +77,7 @@ class CommandWrite extends CommandPolykey { await pkClient.rpcClient.methods.vaultsSecretsWriteFile({ metadata: auth, nameOrId: secretPath[0], - secretName: secretPath[1], + secretName: secretPath[1] ?? '/', secretContent: stdin, }), meta, diff --git a/src/utils/options.ts b/src/utils/options.ts index 69fca4ce..ba831e7c 100644 --- a/src/utils/options.ts +++ b/src/utils/options.ts @@ -310,7 +310,12 @@ const order = new commander.Option( const recursive = new commander.Option( '--recursive', - 'If enabled, specified directories will be removed along with their contents', + 'If enabled, specified operation will be applied recursively to the directory and its contents', +).default(false); + +const parents = new commander.Option( + '--parents', + 'If enabled, create all parent directories as well. If the directories exist, do nothing.', ).default(false); export { @@ -355,4 +360,5 @@ export { limit, order, recursive, + parents, }; diff --git a/src/utils/parsers.ts b/src/utils/parsers.ts index 46c52982..eef2dd50 100644 --- a/src/utils/parsers.ts +++ b/src/utils/parsers.ts @@ -10,7 +10,6 @@ import * as nodesUtils from 'polykey/dist/nodes/utils'; const vaultNameRegex = /^(?!.*[:])[ -~\t\n]*$/s; const secretPathRegex = /^(?!.*[=])[ -~\t\n]*$/s; -const vaultNameSecretPathRegex = /^([\w\-\.]+)(?::([^\0\\=]+))?$/; const secretPathValueRegex = /^([a-zA-Z_][\w]+)?$/; const environmentVariableRegex = /^([a-zA-Z_]+[a-zA-Z0-9_]*)?$/; @@ -67,32 +66,6 @@ function parseCoreCount(v: string): number | undefined { } } -function parseSecretPathOptional( - secretPath: string, -): [string, string?, string?] { - // E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned - // If 'vault1', ['vault1, undefined] is returned - // splits out everything after an `=` separator - const lastEqualIndex = secretPath.lastIndexOf('='); - const splitSecretPath = - lastEqualIndex === -1 - ? secretPath - : secretPath.substring(0, lastEqualIndex); - const value = - lastEqualIndex === -1 - ? undefined - : secretPath.substring(lastEqualIndex + 1); - if (!vaultNameSecretPathRegex.test(splitSecretPath)) { - throw new commander.InvalidArgumentError( - `${secretPath} is not of the format [:][=]`, - ); - } - const [, vaultName, directoryPath] = splitSecretPath.match( - vaultNameSecretPathRegex, - )!; - return [vaultName, directoryPath, value]; -} - function parseVaultName(vaultName: string): string { if (!vaultNameRegex.test(vaultName)) { throw new commander.InvalidArgumentError( @@ -102,19 +75,48 @@ function parseVaultName(vaultName: string): string { return vaultName; } -function parseSecretPath(secretPath: string): [string, string, string?] { - // E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c'] is returned - // If 'vault1', an error is thrown - const [vaultName, secretName, value] = parseSecretPathOptional(secretPath); - if (secretName === undefined) { +// E.g. If 'vault1:a/b/c', ['vault1', 'a/b/c', undefined] is returned +// If 'vault1', ['vault1', undefined, undefined] is returned +// If 'vault1:abc=xyz', ['vault1', 'abc', 'xyz'] is returned +// If 'vault1:', an error is thrown +// If 'a/b/c', an error is thrown +// Splits out everything after an `=` separator +function parseSecretPath(inputPath: string): [string, string?, string?] { + // The colon character `:` is prohibited in vaultName, so it's first occurence + // means that this is the delimiter between vaultName and secretPath. + const colonIndex = inputPath.indexOf(':'); + // If no colon exists, treat entire string as vault name + if (colonIndex === -1) { + return [parseVaultName(inputPath), undefined, undefined]; + } + // Calculate vaultName and secretPath + const vaultNamePart = inputPath.substring(0, colonIndex); + const secretPathPart = inputPath.substring(colonIndex + 1); + // Calculate contents after the `=` separator (value) + const equalIndex = secretPathPart.indexOf('='); + // If `=` isn't found, then the entire secret path section is the actual path. + // Otherwise, split the path section by the index of `=`. First half is the + // actual secret part, and the second half is the value. + const secretPath = + equalIndex === -1 + ? secretPathPart + : secretPathPart.substring(0, equalIndex); + const value = + equalIndex !== -1 ? secretPathPart.substring(equalIndex + 1) : undefined; + // If secretPath exists but it doesn't pass the regex test, then the path is + // malformed. + if (secretPath != null && !secretPathRegex.test(secretPath)) { throw new commander.InvalidArgumentError( - `${secretPath} is not of the format :[=]`, + `${inputPath} is not of the format [:][=]`, ); } - return [vaultName, secretName, value]; + // We have already tested if the secretPath is valid, and we can return it + // as-is. + const vaultName = parseVaultName(vaultNamePart); + return [vaultName, secretPath, value]; } -function parseSecretPathValue(secretPath: string): [string, string, string?] { +function parseSecretPathValue(secretPath: string): [string, string?, string?] { const [vaultName, directoryPath, value] = parseSecretPath(secretPath); if (value != null && !secretPathValueRegex.test(value)) { throw new commander.InvalidArgumentError( @@ -125,36 +127,7 @@ function parseSecretPathValue(secretPath: string): [string, string, string?] { } function parseSecretPathEnv(secretPath: string): [string, string?, string?] { - // The colon character `:` is prohibited in vaultName, so it's first occurence - // means that this is the delimiter between vaultName and secretPath. - const colonIndex = secretPath.indexOf(':'); - // If no colon exists, treat entire string as vault name - if (colonIndex === -1) { - return [parseVaultName(secretPath), undefined, undefined]; - } - // Calculate contents before the `=` separator - const vaultNamePart = secretPath.substring(0, colonIndex); - const secretPathPart = secretPath.substring(colonIndex + 1); - // Calculate contents after the `=` separator - const equalIndex = secretPathPart.indexOf('='); - const splitSecretPath = - equalIndex === -1 - ? secretPathPart - : secretPathPart.substring(0, equalIndex); - const valueData = - equalIndex === -1 ? undefined : secretPathPart.substring(equalIndex + 1); - if (splitSecretPath != null && !secretPathRegex.test(splitSecretPath)) { - throw new commander.InvalidArgumentError( - `${secretPath} is not of the format [:][=]`, - ); - } - const parsedVaultName = parseVaultName(vaultNamePart); - const parsedSecretPath = splitSecretPath.match(secretPathRegex)?.[0] ?? '/'; - const [vaultName, directoryPath, value] = [ - parsedVaultName, - parsedSecretPath, - valueData, - ]; + const [vaultName, directoryPath, value] = parseSecretPath(secretPath); if (value != null && !environmentVariableRegex.test(value)) { throw new commander.InvalidArgumentError( `${value} is not a valid environment variable name`, @@ -263,7 +236,6 @@ export { validateParserToArgParser, validateParserToArgListParser, parseCoreCount, - parseSecretPathOptional, parseVaultName, parseSecretPath, parseSecretPathValue, diff --git a/src/vaults/CommandList.ts b/src/vaults/CommandList.ts index fc9b3311..adb94f03 100644 --- a/src/vaults/CommandList.ts +++ b/src/vaults/CommandList.ts @@ -8,8 +8,9 @@ import * as binProcessors from '../utils/processors'; class CommandList extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); - this.name('list'); - this.description('List all Available Vaults'); + this.name('ls'); + this.alias('list'); + this.description('List all available Vaults'); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); diff --git a/tests/secrets/cat.test.ts b/tests/secrets/cat.test.ts index 77c1cf54..424e0efd 100644 --- a/tests/secrets/cat.test.ts +++ b/tests/secrets/cat.test.ts @@ -40,7 +40,7 @@ describe('commandCatSecret', () => { }); }); test('should retrieve a secret', async () => { - const vaultName = 'Vault3' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret-name'; const secretContent = 'this is the contents of the secret'; @@ -61,6 +61,22 @@ describe('commandCatSecret', () => { expect(result.exitCode).toBe(0); expect(result.stdout).toBe(secretContent); }); + test('should fail when reading root without secret path', async () => { + const vaultName = 'vault' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + const secretName = 'secret-name'; + const secretContent = 'this is the contents of the secret'; + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.addSecret(vault, secretName, secretContent); + }); + const command = ['secrets', 'cat', '-np', dataDir, vaultName]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toInclude('ErrorSecretsIsDirectory'); + }); test('should concatenate multiple secrets', async () => { const vaultName = 'Vault3' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); @@ -132,14 +148,14 @@ describe('commandCatSecret', () => { '-np', dataDir, `${vaultName}:${secretName1}`, - `${vaultName}:doesnt-exist-file`, + `${vaultName}:doesnt-exist`, `${vaultName}:${secretName2}`, ]; const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); - expect(result.exitCode).toBe(0); + expect(result.exitCode).toBe(1); expect(result.stdout).toBe(`${secretName1}${secretName2}`); expect(result.stderr).not.toBe(''); }); @@ -169,7 +185,7 @@ describe('commandCatSecret', () => { resolve(exitCode); }); }); - expect(exitCode).toStrictEqual(0); + expect(exitCode).toBe(0); expect(stdout).toBe(stdinData); }); }); diff --git a/tests/secrets/create.test.ts b/tests/secrets/create.test.ts index 9d2bbde2..76b10708 100644 --- a/tests/secrets/create.test.ts +++ b/tests/secrets/create.test.ts @@ -14,7 +14,9 @@ describe('commandCreateSecret', () => { const logger = new Logger('CLI Test', LogLevel.WARN, [new StreamHandler()]); let dataDir: string; let polykeyAgent: PolykeyAgent; - let command: Array; + + const fileNameArb = fc.stringMatching(/^[^\0\\/=]$/); + const envVariableArb = fc.stringMatching(/^([a-zA-Z_][\w]+)?$/); beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -43,55 +45,68 @@ describe('commandCreateSecret', () => { }); }); - test( - 'should create secrets', - async () => { - const vaultName = 'Vault1' as VaultName; - const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - const secretPath = path.join(dataDir, 'secret'); - await fs.promises.writeFile(secretPath, 'this is a secret'); - - command = [ - 'secrets', - 'create', - '-np', - dataDir, - secretPath, - `${vaultName}:MySecret`, - ]; - - const result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(0); - - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(['MySecret']); - expect( - (await vaultOps.getSecret(vault, 'MySecret')).toString(), - ).toStrictEqual('this is a secret'); - }); - }, - globalThis.defaultTimeout * 2, - ); - const fileNameArb = fc.stringMatching(/^[^\0\\/=]$/); - const envVariableArb = fc.stringMatching(/^([a-zA-Z_][\w]+)?$/); + test('should create secrets', async () => { + const vaultName = 'vault' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + const secretPath = path.join(dataDir, 'secret'); + const vaultSecretName = 'vault-secret'; + const secretContent = 'this is a secret'; + await fs.promises.writeFile(secretPath, secretContent); + const command = [ + 'secrets', + 'create', + '-np', + dataDir, + secretPath, + `${vaultName}:${vaultSecretName}`, + ]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + const list = await vaultOps.listSecrets(vault); + expect(list.sort()).toStrictEqual([vaultSecretName]); + expect( + (await vaultOps.getSecret(vault, vaultSecretName)).toString(), + ).toStrictEqual(secretContent); + }); + }); + test('should fail without providing secret path', async () => { + const vaultName = 'vault' as VaultName; + await polykeyAgent.vaultManager.createVault(vaultName); + const secretName = path.join(dataDir, 'secret'); + await fs.promises.writeFile(secretName, secretName); + const command = [ + 'secrets', + 'create', + '-np', + dataDir, + secretName, + vaultName, + ]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + // The root directory is already defined so we can't create a new secret + // at path `vault:/`. + expect(result.stderr).toInclude('ErrorSecretsSecretDefined'); + }); test.prop([fileNameArb, fileNameArb, envVariableArb], { numRuns: 10 })( 'secrets handle unix style paths for secrets', async (directoryName, secretName, envVariableName) => { await polykeyAgent.vaultManager.stop(); await polykeyAgent.vaultManager.start({ fresh: true }); - const vaultName = 'Vault1' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretPath = path.join(dataDir, 'secret'); - await fs.promises.writeFile(secretPath, 'this is a secret'); + const secretContent = 'this is a secret'; + await fs.promises.writeFile(secretPath, secretContent); const vaultsSecretPath = path.join(directoryName, secretName); - - command = [ + const command = [ 'secrets', 'create', '-np', @@ -99,21 +114,17 @@ describe('commandCreateSecret', () => { secretPath, `${vaultName}:${vaultsSecretPath}=${envVariableName}`, ]; - - const result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([vaultsSecretPath]); expect( (await vaultOps.getSecret(vault, vaultsSecretPath)).toString(), - ).toStrictEqual('this is a secret'); + ).toStrictEqual(secretContent); }); }, ); diff --git a/tests/secrets/edit.test.ts b/tests/secrets/edit.test.ts index e8f3c983..5eebb276 100644 --- a/tests/secrets/edit.test.ts +++ b/tests/secrets/edit.test.ts @@ -17,7 +17,6 @@ describe('commandEditSecret', () => { let editorFail: string; let editorView: string; let polykeyAgent: PolykeyAgent; - let command: Array; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -59,6 +58,7 @@ describe('commandEditSecret', () => { logger: logger, }); }); + afterEach(async () => { await polykeyAgent.stop(); await fs.promises.rm(dataDir, { @@ -68,108 +68,125 @@ describe('commandEditSecret', () => { }); test('should edit secret', async () => { - const vaultName = 'Vault10' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret'; - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, secretName, 'original secret'); }); - - command = ['secrets', 'edit', '-np', dataDir, `${vaultName}:${secretName}`]; - - const result = await testUtils.pkStdio([...command], { + const command = [ + 'secrets', + 'edit', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password, EDITOR: editorEdit }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const contents = await vaultOps.getSecret(vault, secretName); expect(contents.toString()).toStrictEqual(`${editedContent}\n`); }); }); - + test('should fail to edit without a secret path specified', async () => { + const vaultName = 'vault' as VaultName; + const command = ['secrets', 'edit', '-np', dataDir, vaultName]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password, EDITOR: editorEdit }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + }); test('should create secret if it does not exist', async () => { - const vaultName = 'Vault10' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret'; - - command = ['secrets', 'edit', '-np', dataDir, `${vaultName}:${secretName}`]; - - const result = await testUtils.pkStdio([...command], { + const command = [ + 'secrets', + 'edit', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password, EDITOR: editorEdit }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const contents = await vaultOps.getSecret(vault, secretName); expect(contents.toString()).toStrictEqual(`${editedContent}\n`); }); }); - test('should not create secret if editor crashes', async () => { - const vaultName = 'Vault10' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret'; - - command = ['secrets', 'edit', '-np', dataDir, `${vaultName}:${secretName}`]; - - const result = await testUtils.pkStdio([...command], { + const command = [ + 'secrets', + 'edit', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password, EDITOR: editorFail }, cwd: dataDir, }); expect(result.exitCode).not.toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); }); }); - test('should not create secret if editor does not write to file', async () => { - const vaultName = 'Vault10' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret'; - - command = ['secrets', 'edit', '-np', dataDir, `${vaultName}:${secretName}`]; - - const result = await testUtils.pkStdio([...command], { + const command = [ + 'secrets', + 'edit', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password, EDITOR: editorExit }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); }); }); - test('file contents should be fetched correctly', async () => { - const vaultName = 'Vault10' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); const secretName = 'secret'; const secretContent = 'original secret'; - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, secretName, secretContent); }); - - command = ['secrets', 'edit', '-np', dataDir, `${vaultName}:${secretName}`]; - - const result = await testUtils.pkStdio([...command], { + const command = [ + 'secrets', + 'edit', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password, EDITOR: editorView }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - const fetchedSecret = await fs.promises.readFile( path.join(dataDir, 'secret'), ); expect(fetchedSecret.toString()).toEqual(secretContent); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const contents = await vaultOps.getSecret(vault, secretName); expect(contents.toString()).toStrictEqual(`${editedContent}\n`); diff --git a/tests/secrets/env.test.ts b/tests/secrets/env.test.ts index a42f8a8f..f84b958f 100644 --- a/tests/secrets/env.test.ts +++ b/tests/secrets/env.test.ts @@ -17,7 +17,6 @@ describe('commandEnv', () => { const vaultName = 'vault' as VaultName; let dataDir: string; let polykeyAgent: PolykeyAgent; - let command: Array; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -48,12 +47,10 @@ describe('commandEnv', () => { test('can select 1 env variable', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET', 'this is the secret1'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -66,8 +63,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -76,13 +72,11 @@ describe('commandEnv', () => { }); test('can select multiple env variables', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'SECRET2', 'this is the secret2'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -96,8 +90,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -107,15 +100,13 @@ describe('commandEnv', () => { }); test('can select a directory of env variables', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.mkdir(vault, 'dir1'); await vaultOps.addSecret(vault, 'dir1/SECRET2', 'this is the secret2'); await vaultOps.addSecret(vault, 'dir1/SECRET3', 'this is the secret3'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -128,8 +119,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -140,12 +130,10 @@ describe('commandEnv', () => { }); test('can select and rename an env variable', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET', 'this is the secret'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -158,8 +146,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -169,14 +156,12 @@ describe('commandEnv', () => { }); test('can not rename a directory of env variables', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.mkdir(vault, 'dir'); await vaultOps.addSecret(vault, 'dir1/SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'dir1/SECRET2', 'this is the secret2'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -189,8 +174,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -201,7 +185,6 @@ describe('commandEnv', () => { }); test('can mix and match env variables', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'SECRET2', 'this is the secret2'); @@ -209,8 +192,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault, 'dir1/SECRET3', 'this is the secret3'); await vaultOps.addSecret(vault, 'dir1/SECRET4', 'this is the secret4'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -225,8 +207,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -238,12 +219,10 @@ describe('commandEnv', () => { }); test('existing env are passed through', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -256,8 +235,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password, EXISTING: 'existing var', @@ -270,7 +248,6 @@ describe('commandEnv', () => { }); test('handles duplicate secret names', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'SECRET2', 'this is the secret2'); @@ -278,8 +255,7 @@ describe('commandEnv', () => { await vaultOps.mkdir(vault, 'dir1'); await vaultOps.addSecret(vault, 'dir1/SECRET4', 'this is the secret4'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -295,8 +271,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -309,7 +284,6 @@ describe('commandEnv', () => { }); test('should output human format', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'SECRET2', 'this is the secret2'); @@ -317,8 +291,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault, 'dir1/SECRET3', 'this is the secret3'); await vaultOps.addSecret(vault, 'dir1/SECRET4', 'this is the secret4'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -327,8 +300,7 @@ describe('commandEnv', () => { 'unix', `${vaultName}`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -344,7 +316,6 @@ describe('commandEnv', () => { const vaultId2 = await polykeyAgent.vaultManager.createVault( `${vaultName}2`, ); - await polykeyAgent.vaultManager.withVaults( [vaultId1, vaultId2], async (vault1, vault2) => { @@ -356,8 +327,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault2, 'dir1/SECRET4', 'this is the secret4'); }, ); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -367,8 +337,7 @@ describe('commandEnv', () => { `${vaultName}1`, `${vaultName}2`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -384,7 +353,6 @@ describe('commandEnv', () => { const vaultId2 = await polykeyAgent.vaultManager.createVault( `${vaultName}2`, ); - await polykeyAgent.vaultManager.withVaults( [vaultId1, vaultId2], async (vault1, vault2) => { @@ -396,8 +364,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault2, 'dir1/SECRET4', 'this is the secret4'); }, ); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -407,8 +374,7 @@ describe('commandEnv', () => { `${vaultName}1`, `${vaultName}2`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -424,7 +390,6 @@ describe('commandEnv', () => { const vaultId2 = await polykeyAgent.vaultManager.createVault( `${vaultName}2`, ); - await polykeyAgent.vaultManager.withVaults( [vaultId1, vaultId2], async (vault1, vault2) => { @@ -436,8 +401,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault2, 'dir1/SECRET4', 'this is the secret4'); }, ); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -447,8 +411,7 @@ describe('commandEnv', () => { `${vaultName}1`, `${vaultName}2`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -459,7 +422,6 @@ describe('commandEnv', () => { }); test('should output json format', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET1', 'this is the secret1'); await vaultOps.addSecret(vault, 'SECRET2', 'this is the secret2'); @@ -467,8 +429,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault, 'dir1/SECRET3', 'this is the secret3'); await vaultOps.addSecret(vault, 'dir1/SECRET4', 'this is the secret4'); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -477,8 +438,7 @@ describe('commandEnv', () => { 'json', `${vaultName}`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -491,11 +451,9 @@ describe('commandEnv', () => { }); test('testing valid and invalid rename inputs', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'SECRET', 'this is the secret'); }); - const valid = [ 'one', 'ONE', @@ -506,88 +464,79 @@ describe('commandEnv', () => { 'ONE123', 'ONE_123', ]; - const invalid = ['123', '123abc', '123_123', '123_abc', '123 abc', ' ']; - + const validCommand = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + ...valid.map((v) => `${vaultName}:SECRET=${v}`), + ]; // Checking valid - const result = await testUtils.pkExec( - [ + const result = await testUtils.pkExec(validCommand, { + env: { PK_PASSWORD: password }, + }); + expect(result.exitCode).toBe(0); + // Checking invalid + for (const nameNew of invalid) { + const invalidCommand = [ 'secrets', 'env', '-np', dataDir, '--env-format', 'unix', - ...valid.map((v) => `${vaultName}:SECRET=${v}`), - ], - { env: { PK_PASSWORD: password } }, - ); - expect(result.exitCode).toBe(0); - - // Checking invalid - for (const nameNew of invalid) { - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-e', - `${vaultName}:SECRET=${nameNew}`, - ], - { env: { PK_PASSWORD: password } }, - ); + '-e', + `${vaultName}:SECRET=${nameNew}`, + ]; + const result = await testUtils.pkExec(invalidCommand, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(sysexits.USAGE); } }); test('invalid handled with error', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, '123', 'this is an invalid secret'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ei', - 'error', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ei', + 'error', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(64); }); test('invalid handled with warn', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, '123', 'this is an invalid secret'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ei', - 'warn', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ei', + 'warn', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(0); expect(result.stdout).toBe(''); expect(result.stderr).toInclude( @@ -596,26 +545,24 @@ describe('commandEnv', () => { }); test('invalid handled with ignore', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, '123', 'this is an invalid secret'); }); - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ei', - 'ignore', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ei', + 'ignore', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(0); expect(result.stdout).toBe(''); expect(result.stderr).not.toInclude( @@ -624,55 +571,49 @@ describe('commandEnv', () => { }); test('duplicate handled with error', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'secret', 'this is a secret'); await vaultOps.mkdir(vault, 'dir'); await vaultOps.addSecret(vault, 'dir/secret', 'this is a secret'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ed', - 'error', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ed', + 'error', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(64); expect(result.stderr).toInclude('ErrorPolykeyCLIDuplicateEnvName'); }); test('duplicate handled with warn', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'secret', 'this is a secret'); await vaultOps.mkdir(vault, 'dir'); await vaultOps.addSecret(vault, 'dir/secret', 'this is a secret'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ed', - 'warn', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ed', + 'warn', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(0); expect(result.stderr).toInclude( 'The env variable (secret) is duplicate, overwriting', @@ -680,61 +621,54 @@ describe('commandEnv', () => { }); test('duplicate handled with keep', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'secret', 'this is a secret1'); await vaultOps.mkdir(vault, 'dir'); await vaultOps.addSecret(vault, 'dir/secret', 'this is a secret2'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ed', - 'keep', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ed', + 'keep', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(0); expect(result.stdout).toInclude('this is a secret1'); }); test('duplicate handled with overwrite', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret(vault, 'secret', 'this is a secret1'); await vaultOps.mkdir(vault, 'dir'); await vaultOps.addSecret(vault, 'dir/secret', 'this is a secret2'); }); - - // Checking valid - const result = await testUtils.pkExec( - [ - 'secrets', - 'env', - '-np', - dataDir, - '--env-format', - 'unix', - '-ed', - 'overwrite', - `${vaultName}`, - ], - { env: { PK_PASSWORD: password } }, - ); + const command = [ + 'secrets', + 'env', + '-np', + dataDir, + '--env-format', + 'unix', + '-ed', + 'overwrite', + `${vaultName}`, + ]; + const result = await testUtils.pkExec(command, { + env: { PK_PASSWORD: password }, + }); expect(result.exitCode).toBe(0); expect(result.stdout).toInclude('this is a secret2'); }); test('newlines in secrets are untouched', async () => { const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { await vaultOps.addSecret( vault, @@ -742,8 +676,7 @@ describe('commandEnv', () => { 'this is a secret\nit has multiple lines\n', ); }); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -756,8 +689,7 @@ describe('commandEnv', () => { '-e', 'console.log(JSON.stringify(process.env))', ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); @@ -792,15 +724,14 @@ describe('commandEnv', () => { }, ); test('handles no arguments', async () => { - command = ['secrets', 'env', '-np', dataDir, '--env-format', 'unix']; - - const result1 = await testUtils.pkExec([...command], { + const command = ['secrets', 'env', '-np', dataDir, '--env-format', 'unix']; + const result1 = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result1.exitCode).toBe(64); }); test('handles providing no secret paths', async () => { - command = [ + const command = [ 'secrets', 'env', '-np', @@ -810,8 +741,7 @@ describe('commandEnv', () => { '--', 'someCommand', ]; - - const result1 = await testUtils.pkExec([...command], { + const result1 = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result1.exitCode).toBe(64); @@ -823,7 +753,6 @@ describe('commandEnv', () => { const vaultId2 = await polykeyAgent.vaultManager.createVault( `${vaultName}2`, ); - await polykeyAgent.vaultManager.withVaults( [vaultId1, vaultId2], async (vault1, vault2) => { @@ -835,8 +764,7 @@ describe('commandEnv', () => { await vaultOps.addSecret(vault2, 'dir1/SECRET4', 'this is the secret4'); }, ); - - command = [ + const command = [ 'secrets', 'env', '-np', @@ -846,8 +774,7 @@ describe('commandEnv', () => { `${vaultName}1`, `${vaultName}2`, ]; - - const result = await testUtils.pkExec([...command], { + const result = await testUtils.pkExec(command, { env: { PK_PASSWORD: password }, }); expect(result.exitCode).toBe(0); diff --git a/tests/secrets/list.test.ts b/tests/secrets/list.test.ts index 3e8513c6..6a203738 100644 --- a/tests/secrets/list.test.ts +++ b/tests/secrets/list.test.ts @@ -12,7 +12,6 @@ describe('commandListSecrets', () => { const logger = new Logger('CLI Test', LogLevel.WARN, [new StreamHandler()]); let dataDir: string; let polykeyAgent: PolykeyAgent; - let command: Array; beforeEach(async () => { dataDir = await fs.promises.mkdtemp( @@ -41,124 +40,121 @@ describe('commandListSecrets', () => { }); }); - test( - 'should fail when vault does not exist', - async () => { - command = ['secrets', 'ls', '-np', dataDir, 'DoesntExist']; - const result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(64); // Sysexits.USAGE - }, - globalThis.defaultTimeout * 2, - ); - - test( - 'should list secrets', - async () => { - const vaultName = 'Vault4' as VaultName; - const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecret(vault, 'MySecret1', ''); - await vaultOps.addSecret(vault, 'MySecret2', ''); - await vaultOps.addSecret(vault, 'MySecret3', ''); - }); - - command = ['secrets', 'ls', '-np', dataDir, vaultName]; - const result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe('MySecret1\nMySecret2\nMySecret3\n'); - }, - globalThis.defaultTimeout * 2, - ); - - test( - 'should fail when path is not a directory', - async () => { - const vaultName = 'Vault4' as VaultName; - const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.mkdir(vault, 'SecretDir'); - await vaultOps.addSecret(vault, 'SecretDir/MySecret1', ''); - await vaultOps.addSecret(vault, 'SecretDir/MySecret2', ''); - await vaultOps.addSecret(vault, 'SecretDir/MySecret3', ''); - }); - - command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:WrongDirName`]; - let result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(64); - - command = [ - 'secrets', - 'ls', - '-np', - dataDir, - `${vaultName}:SecretDir/MySecret1`, - ]; - result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(64); - }, - globalThis.defaultTimeout * 2, - ); - - test( - 'should list secrets within directories', - async () => { - const vaultName = 'Vault4' as VaultName; - const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.mkdir(vault, 'SecretDir/NestedDir', { recursive: true }); - await vaultOps.addSecret(vault, 'SecretDir/MySecret1', ''); - await vaultOps.addSecret(vault, 'SecretDir/NestedDir/MySecret2', ''); - }); - - command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:SecretDir`]; - let result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe('SecretDir/MySecret1\nSecretDir/NestedDir\n'); + test('should fail when vault does not exist', async () => { + const command = ['secrets', 'ls', '-np', dataDir, 'doesnt-exist']; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + }); + test('should list root contents without specifying secret path', async () => { + const vaultName = 'vault' as VaultName; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.addSecret(vault, secretName1, ''); + await vaultOps.addSecret(vault, secretName2, ''); + await vaultOps.addSecret(vault, secretName3, ''); + }); + const command = ['secrets', 'ls', '-np', dataDir, vaultName]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.trim().split('\n')).toEqual([ + secretName1, + secretName2, + secretName3, + ]); + }); + test('should list secrets', async () => { + const vaultName = 'vault' as VaultName; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.addSecret(vault, secretName1, ''); + await vaultOps.addSecret(vault, secretName2, ''); + await vaultOps.addSecret(vault, secretName3, ''); + }); + const command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:.`]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.trim().split('\n')).toEqual([ + secretName1, + secretName2, + secretName3, + ]); + }); + test('should fail when path is not a directory', async () => { + const vaultName = 'vault' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + const dirName = 'dir'; + const secretName = 'secret1'; + const secretDirName = path.join(dirName, secretName); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.mkdir(vault, dirName); + await vaultOps.addSecret(vault, secretDirName, ''); + }); + let command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:nodir`]; + let result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + command = [ + 'secrets', + 'ls', + '-np', + dataDir, + `${vaultName}:${secretDirName}`, + ]; + result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + }); - command = [ - 'secrets', - 'ls', - '-np', - dataDir, - `${vaultName}:SecretDir/NestedDir`, - ]; - result = await testUtils.pkStdio([...command], { - env: { - PK_PASSWORD: password, - }, - cwd: dataDir, - }); - expect(result.exitCode).toBe(0); - expect(result.stdout).toBe('SecretDir/NestedDir/MySecret2\n'); - }, - globalThis.defaultTimeout * 2, - ); + test('should list secrets within directories', async () => { + const vaultName = 'Vault4' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + const dirName1 = 'dir1'; + const dirName2 = 'dir2'; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const nestedDir = path.join(dirName1, dirName2); + const secretDirName1 = path.join(dirName1, secretName1); + const secretDirName2 = path.join(nestedDir, secretName2); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.mkdir(vault, nestedDir, { recursive: true }); + await vaultOps.addSecret(vault, secretDirName1, ''); + await vaultOps.addSecret(vault, secretDirName2, ''); + }); + let command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:${dirName1}`]; + let result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout.trim().split('\n')).toEqual([ + nestedDir, + secretDirName1, + ]); + command = ['secrets', 'ls', '-np', dataDir, `${vaultName}:${nestedDir}`]; + result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toBe(`${secretDirName2}\n`); + }); }); diff --git a/tests/secrets/mkdir.test.ts b/tests/secrets/mkdir.test.ts index 831386a6..956facaa 100644 --- a/tests/secrets/mkdir.test.ts +++ b/tests/secrets/mkdir.test.ts @@ -46,7 +46,7 @@ describe('commandMkdir', () => { const dirName = 'dir'; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); command = ['secrets', 'mkdir', '-np', dataDir, `${vaultName}:${dirName}`]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -56,6 +56,20 @@ describe('commandMkdir', () => { expect(stat.isDirectory()).toBeTruthy(); }); }); + test('should fail when provided only the vault name', async () => { + const vaultName = 'vault' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + command = ['secrets', 'mkdir', '-np', dataDir, vaultName]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toInclude('EEXIST'); // Root directory is already a directory + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + expect(await vaultOps.listSecrets(vault)).toEqual([]); + }); + }); test('should make directories recursively', async () => { const vaultName = 'vault' as VaultName; const dirName1 = 'dir1'; @@ -68,9 +82,9 @@ describe('commandMkdir', () => { '-np', dataDir, `${vaultName}:${dirNameNested}`, - '--recursive', + '--parents', ]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -95,7 +109,7 @@ describe('commandMkdir', () => { dataDir, `${vaultName}:${dirNameNested}`, ]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -120,7 +134,7 @@ describe('commandMkdir', () => { }); }); command = ['secrets', 'mkdir', '-np', dataDir, `${vaultName}:${dirName}`]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -150,7 +164,7 @@ describe('commandMkdir', () => { dataDir, `${vaultName}:${secretName}`, ]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -182,7 +196,7 @@ describe('commandMkdir', () => { `${vaultName2}:${dirName2}`, `${vaultName1}:${dirName3}`, ]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); @@ -218,7 +232,7 @@ describe('commandMkdir', () => { `${vaultName2}:${dirName3}`, `${vaultName1}:${dirName4}`, ]; - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); diff --git a/tests/secrets/newDirSecret.test.ts b/tests/secrets/newDirSecret.test.ts index 65a608dd..d6df35fb 100644 --- a/tests/secrets/newDirSecret.test.ts +++ b/tests/secrets/newDirSecret.test.ts @@ -42,43 +42,38 @@ describe('commandNewDirSecret', () => { }); test('should add a directory of secrets', async () => { - const vaultName = 'Vault8' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - - const secretDir = path.join(dataDir, 'secrets'); - await fs.promises.mkdir(secretDir); - await fs.promises.writeFile( - path.join(secretDir, 'secret-1'), - 'this is the secret 1', - ); - await fs.promises.writeFile( - path.join(secretDir, 'secret-2'), - 'this is the secret 2', - ); - await fs.promises.writeFile( - path.join(secretDir, 'secret-3'), - 'this is the secret 3', - ); - + const secretDirName = 'secrets'; + const secretDirPathName = path.join(dataDir, secretDirName); + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const secretPath1 = path.join(secretDirPathName, secretName1); + const secretPath2 = path.join(secretDirPathName, secretName2); + const secretPath3 = path.join(secretDirPathName, secretName3); + await fs.promises.mkdir(secretDirPathName); + await fs.promises.writeFile(secretPath1, secretPath1); + await fs.promises.writeFile(secretPath2, secretPath2); + await fs.promises.writeFile(secretPath3, secretPath3); await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); }); - - command = ['secrets', 'dir', '-np', dataDir, secretDir, vaultName]; - - const result2 = await testUtils.pkStdio([...command], { + command = ['secrets', 'dir', '-np', dataDir, secretDirPathName, vaultName]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); - expect(result2.exitCode).toBe(0); - + expect(result.exitCode).toBe(0); await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); + // The secretPath includes the full local path. We need relative vault + // path, which only needs the actual directory name and the secret name. expect(list.sort()).toStrictEqual([ - 'secrets/secret-1', - 'secrets/secret-2', - 'secrets/secret-3', + path.join(secretDirName, secretName1), + path.join(secretDirName, secretName2), + path.join(secretDirName, secretName3), ]); }); }); diff --git a/tests/secrets/remove.test.ts b/tests/secrets/remove.test.ts index 561c57d2..f19bcb72 100644 --- a/tests/secrets/remove.test.ts +++ b/tests/secrets/remove.test.ts @@ -41,203 +41,269 @@ describe('commandRemoveSecret', () => { }); test('should remove secret', async () => { - const vaultName = 'Vault2' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - + const secretName = 'secret'; + const secretContent = 'this is the secret'; await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecret(vault, 'MySecret', 'this is the secret'); + await vaultOps.addSecret(vault, secretName, secretContent); const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(['MySecret']); + expect(list.sort()).toStrictEqual([secretName]); }); - - const command = ['secrets', 'rm', '-np', dataDir, `${vaultName}:MySecret`]; - + const command = [ + 'secrets', + 'rm', + '-np', + dataDir, + `${vaultName}:${secretName}`, + ]; const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); }); }); - + test('should fail to remove vault root', async () => { + const vaultName = 'vault' as VaultName; + const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); + const secretName = 'secret'; + const secretContent = 'this is the secret'; + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + await vaultOps.addSecret(vault, secretName, secretContent); + const list = await vaultOps.listSecrets(vault); + expect(list.sort()).toStrictEqual([secretName]); + }); + const command = ['secrets', 'rm', '-np', dataDir, vaultName]; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toInclude('EPERM'); + await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { + const list = await vaultOps.listSecrets(vault); + expect(list.sort()).toStrictEqual([secretName]); + }); + }); test('should remove multiple secrets', async () => { - const vaultName = 'Vault2' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - const secretNames = ['secret1', 'secret2', 'secret3']; - + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const secretName4 = 'secret4'; await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - for (const secretName of secretNames) { - await vaultOps.addSecret(vault, secretName, secretName); - } + await vaultOps.addSecret(vault, secretName1, secretName1); + await vaultOps.addSecret(vault, secretName2, secretName2); + await vaultOps.addSecret(vault, secretName3, secretName3); + await vaultOps.addSecret(vault, secretName4, secretName4); const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames); + expect(list.sort()).toStrictEqual([ + secretName1, + secretName2, + secretName3, + secretName4, + ]); }); - - const secretPaths = secretNames.map((v) => `${vaultName}:${v}`); - const command = ['secrets', 'rm', '-np', dataDir, ...secretPaths]; - + const command = [ + 'secrets', + 'rm', + '-np', + dataDir, + `${vaultName}:${secretName1}`, + `${vaultName}:${secretName2}`, + `${vaultName}:${secretName3}`, + ]; const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual([]); + expect(list.sort()).toStrictEqual([secretName4]); }); }); - test('should make one log message for deleting multiple secrets', async () => { - const vaultName = 'Vault2' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - const secretNames = ['secret1', 'secret2', 'secret3']; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; let vaultLogLength: number; - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - for (const secretName of secretNames) { - await vaultOps.addSecret(vault, secretName, secretName); - } + await vaultOps.addSecret(vault, secretName1, secretName1); + await vaultOps.addSecret(vault, secretName2, secretName2); + await vaultOps.addSecret(vault, secretName3, secretName3); vaultLogLength = (await vault.log()).length; const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames); + expect(list.sort()).toStrictEqual([ + secretName1, + secretName2, + secretName3, + ]); }); - - const secretPaths = secretNames.map((v) => `${vaultName}:${v}`); - const command = ['secrets', 'rm', '-np', dataDir, ...secretPaths]; - + const command = [ + 'secrets', + 'rm', + '-np', + dataDir, + `${vaultName}:${secretName1}`, + `${vaultName}:${secretName2}`, + `${vaultName}:${secretName3}`, + ]; const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); expect((await vault.log()).length).toEqual(vaultLogLength + 1); }); }); - test('should remove secrets recursively', async () => { - const vaultName = 'Vault2' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - const secretDir = 'secretDir'; - const secretNames = ['secret1', 'secret2', 'secret3'].map((v) => - path.join(secretDir, v), - ); - + const secretDirName = 'dir'; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const secretPath1 = path.join(secretDirName, secretName1); + const secretPath2 = path.join(secretDirName, secretName2); + const secretPath3 = path.join(secretDirName, secretName3); await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.mkdir(vault, secretDir); - for (const secretName of secretNames) { - await vaultOps.addSecret(vault, secretName, secretName); - } + await vaultOps.mkdir(vault, secretDirName); + await vaultOps.addSecret(vault, secretPath1, secretPath1); + await vaultOps.addSecret(vault, secretPath2, secretPath2); + await vaultOps.addSecret(vault, secretPath3, secretPath3); const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames); + expect(list.sort()).toStrictEqual([ + secretPath1, + secretPath2, + secretPath3, + ]); }); - const command = [ 'secrets', 'rm', '-np', dataDir, - `${vaultName}:secretDir`, + `${vaultName}:${secretDirName}`, '--recursive', ]; - const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); expect(list.sort()).toStrictEqual([]); }); }); - test('should fail to remove directory without recursive flag', async () => { - const vaultName = 'Vault2' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - const secretDir = 'secretDir'; - const secretNames = ['secret1', 'secret2', 'secret3'].map((v) => - path.join(secretDir, v), - ); - + const secretDirName = 'dir'; + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const secretPath1 = path.join(secretDirName, secretName1); + const secretPath2 = path.join(secretDirName, secretName2); + const secretPath3 = path.join(secretDirName, secretName3); await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.mkdir(vault, secretDir); - for (const secretName of secretNames) { - await vaultOps.addSecret(vault, secretName, secretName); - } + await vaultOps.mkdir(vault, secretDirName); + await vaultOps.addSecret(vault, secretPath1, secretPath1); + await vaultOps.addSecret(vault, secretPath2, secretPath2); + await vaultOps.addSecret(vault, secretPath3, secretPath3); const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames); + expect(list.sort()).toStrictEqual([ + secretPath1, + secretPath2, + secretPath3, + ]); }); - - const command = ['secrets', 'rm', '-np', dataDir, `${vaultName}:secretDir`]; - + const command = [ + 'secrets', + 'rm', + '-np', + dataDir, + `${vaultName}:${secretDirName}`, + ]; const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).not.toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames); + expect(list.sort()).toStrictEqual([ + secretPath1, + secretPath2, + secretPath3, + ]); }); }); - test('should remove files from multiple vaults in the same command', async () => { - const vaultName1 = 'Vault2-1' as VaultName; - const vaultName2 = 'Vault2-2' as VaultName; + const vaultName1 = 'vault-1' as VaultName; + const vaultName2 = 'vault-2' as VaultName; const vaultId1 = await polykeyAgent.vaultManager.createVault(vaultName1); const vaultId2 = await polykeyAgent.vaultManager.createVault(vaultName2); - const secretNames1 = ['secret1', 'secret2', 'secret3']; - const secretNames2 = ['secret4', 'secret5']; - - await polykeyAgent.vaultManager.withVaults([vaultId1], async (vault) => { - for (const secretName of secretNames1) { - await vaultOps.addSecret(vault, secretName, secretName); - } - const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames1); - }); - - await polykeyAgent.vaultManager.withVaults([vaultId2], async (vault) => { - for (const secretName of secretNames2) { - await vaultOps.addSecret(vault, secretName, secretName); - } - const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(secretNames2); - }); - + const secretName1 = 'secret1'; + const secretName2 = 'secret2'; + const secretName3 = 'secret3'; + const secretName4 = 'secret4'; + const secretName5 = 'secret5'; + const secretName6 = 'secret6'; + await polykeyAgent.vaultManager.withVaults( + [vaultId1, vaultId2], + async (vault1, vault2) => { + await vaultOps.addSecret(vault1, secretName1, secretName1); + await vaultOps.addSecret(vault1, secretName2, secretName2); + await vaultOps.addSecret(vault1, secretName3, secretName3); + await vaultOps.addSecret(vault1, secretName4, secretName4); + await vaultOps.addSecret(vault2, secretName5, secretName5); + await vaultOps.addSecret(vault2, secretName6, secretName6); + const list1 = await vaultOps.listSecrets(vault1); + expect(list1.sort()).toStrictEqual([ + secretName1, + secretName2, + secretName3, + secretName4, + ]); + const list2 = await vaultOps.listSecrets(vault2); + expect(list2.sort()).toStrictEqual([secretName5, secretName6]); + }, + ); const command = [ 'secrets', 'rm', '-np', dataDir, - ...secretNames1.map((v) => `${vaultName1}:${v}`), - ...secretNames2.map((v) => `${vaultName2}:${v}`), + `${vaultName1}:${secretName1}`, + `${vaultName1}:${secretName2}`, + `${vaultName1}:${secretName3}`, + `${vaultName2}:${secretName5}`, ]; - const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - - await polykeyAgent.vaultManager.withVaults([vaultId1], async (vault) => { - const list = await vaultOps.listSecrets(vault); - expect(list).toStrictEqual([]); - }); - await polykeyAgent.vaultManager.withVaults([vaultId2], async (vault) => { - const list = await vaultOps.listSecrets(vault); - expect(list).toStrictEqual([]); - }); + await polykeyAgent.vaultManager.withVaults( + [vaultId1, vaultId2], + async (vault1, vault2) => { + const list1 = await vaultOps.listSecrets(vault1); + expect(list1).toStrictEqual([secretName4]); + const list2 = await vaultOps.listSecrets(vault2); + expect(list2).toStrictEqual([secretName6]); + }, + ); }); }); diff --git a/tests/secrets/rename.test.ts b/tests/secrets/rename.test.ts index dfd540ea..71bd38b7 100644 --- a/tests/secrets/rename.test.ts +++ b/tests/secrets/rename.test.ts @@ -42,31 +42,41 @@ describe('commandRenameSecret', () => { }); test('should rename secrets', async () => { - const vaultName = 'Vault6' as VaultName; + const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - + const secretName = 'secret'; + const newSecretName = 'secret-renamed'; + const secretContent = 'this is the secret'; await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecret(vault, 'MySecret', 'this is the secret'); + await vaultOps.addSecret(vault, secretName, secretContent); }); - command = [ 'secrets', 'rename', '-np', dataDir, - `${vaultName}:MySecret`, - 'MyRenamedSecret', + `${vaultName}:${secretName}`, + newSecretName, ]; - - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); - await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { const list = await vaultOps.listSecrets(vault); - expect(list.sort()).toStrictEqual(['MyRenamedSecret']); + expect(list.sort()).toStrictEqual([newSecretName]); + }); + }); + test('should not rename vault root', async () => { + const vaultName = 'vault' as VaultName; + await polykeyAgent.vaultManager.createVault(vaultName); + command = ['secrets', 'rename', '-np', dataDir, vaultName, 'rename']; + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, }); + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toInclude('EPERM'); }); }); diff --git a/tests/secrets/stat.test.ts b/tests/secrets/stat.test.ts index 39438fbd..25da704f 100644 --- a/tests/secrets/stat.test.ts +++ b/tests/secrets/stat.test.ts @@ -40,35 +40,58 @@ describe('commandStat', () => { }); }); - test('should retrieve secrets', async () => { - const vaultName = 'Vault9'; + test('should stat secrets', async () => { + const vaultName = 'vault'; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); - + const secretName = 'secret'; await polykeyAgent.vaultManager.withVaults([vaultId], async (vault) => { - await vaultOps.addSecret(vault, 'MySecret', 'this is the secret'); + await vaultOps.addSecret(vault, secretName, secretName); }); - command = [ 'secrets', 'stat', '-np', dataDir, - `${vaultName}:MySecret`, + `${vaultName}:${secretName}`, '--format', 'json', ]; - - const result = await testUtils.pkStdio([...command], { + const result = await testUtils.pkStdio(command, { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }); + expect(result.exitCode).toBe(0); + expect(JSON.parse(result.stdout)).toMatchObject({ + stat: { + nlink: 1, // Reference to itself + blocks: 1, // Each block can store 512 bytes + blksize: 4096, // Default unix block size + size: secretName.length, // Each character is a byte + }, + }); + }); + test('should stat vault root', async () => { + const vaultName = 'vault'; + await polykeyAgent.vaultManager.createVault(vaultName); + command = [ + 'secrets', + 'stat', + '-np', + dataDir, + vaultName, + '--format', + 'json', + ]; + const result = await testUtils.pkStdio(command, { env: { PK_PASSWORD: password }, cwd: dataDir, }); expect(result.exitCode).toBe(0); expect(JSON.parse(result.stdout)).toMatchObject({ stat: { - nlink: 1, - blocks: 1, - blksize: 4096, - size: 18, + nlink: 2, // . and .. (itself and parent) + size: 0, // Directories have no size + blocks: 0, // Too small to occupy a block }, }); }); diff --git a/tests/secrets/write.test.ts b/tests/secrets/write.test.ts index 696a8ec1..a9fe7bf0 100644 --- a/tests/secrets/write.test.ts +++ b/tests/secrets/write.test.ts @@ -68,7 +68,6 @@ describe('commandWriteFile', () => { dataDir, `${vaultName}:${secretName}`, ]; - const childProcess = await testUtils.pkSpawn( command, { @@ -95,6 +94,34 @@ describe('commandWriteFile', () => { }); }, ); + test.prop([stdinArb], { numRuns: 1 })( + 'should fail writing when secret path is not specified', + async (stdinData) => { + const vaultName = genVaultName(); + await polykeyAgent.vaultManager.createVault(vaultName); + command = ['secrets', 'write', '-np', dataDir, vaultName]; + const childProcess = await testUtils.pkSpawn( + command, + { + env: { PK_PASSWORD: password }, + cwd: dataDir, + }, + logger, + ); + // The conditions of stdin being null will not be met in the test, so we + // don't have to worry about the fields being null. + childProcess.stdin!.write(stdinData); + childProcess.stdin!.end(); + const exitCode = await new Promise((resolve) => { + childProcess.once('exit', (code) => { + const exitCode = code ?? -255; + childProcess.removeAllListeners('data'); + resolve(exitCode); + }); + }); + expect(exitCode).not.toBe(0); + }, + ); test('should overwrite secret', async () => { const vaultName = 'vault' as VaultName; const vaultId = await polykeyAgent.vaultManager.createVault(vaultName); @@ -107,7 +134,6 @@ describe('commandWriteFile', () => { dataDir, `${vaultName}:${secretName}`, ]; - const childProcess = await testUtils.pkSpawn( command, { diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 3b1fe3b6..63dee511 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,23 +1,26 @@ import type { Host, Port } from 'polykey/dist/network/types'; +import path from 'path'; import ErrorPolykey from 'polykey/dist/ErrorPolykey'; +import { test } from '@fast-check/jest'; import * as ids from 'polykey/dist/ids'; import * as nodesUtils from 'polykey/dist/nodes/utils'; import * as polykeyErrors from 'polykey/dist/errors'; import * as fc from 'fast-check'; import * as binUtils from '@/utils/utils'; +import * as binParsers from '@/utils/parsers'; -const nonPrintableCharArb = fc - .oneof( - fc.integer({ min: 0, max: 0x1f }), - fc.integer({ min: 0x7f, max: 0x9f }), - ) - .map((code) => String.fromCharCode(code)); +describe('outputFormatters', () => { + const nonPrintableCharArb = fc + .oneof( + fc.integer({ min: 0, max: 0x1f }), + fc.integer({ min: 0x7f, max: 0x9f }), + ) + .map((code) => String.fromCharCode(code)); -const stringWithNonPrintableCharsArb = fc.stringOf( - fc.oneof(fc.char(), nonPrintableCharArb), -); + const stringWithNonPrintableCharsArb = fc.stringOf( + fc.oneof(fc.char(), nonPrintableCharArb), + ); -describe('bin/utils', () => { test('list in human and json format', () => { // List expect( @@ -164,36 +167,26 @@ describe('bin/utils', () => { ' key9\tvalue\n', ); }); - test('outputFormatter should encode non-printable characters within a dict', () => { - fc.assert( - fc.property( - stringWithNonPrintableCharsArb, - stringWithNonPrintableCharsArb, - (key, value) => { - const formattedOutput = binUtils.outputFormatter({ - type: 'dict', - data: { [key]: value }, - }); - - const expectedKey = binUtils.encodeEscapedWrapped(key); - - // Construct the expected output - let expectedValue = value; - expectedValue = binUtils.encodeEscapedWrapped(expectedValue); - expectedValue = expectedValue.replace(/(?:\r\n|\n)$/, ''); - expectedValue = expectedValue.replace(/(\r\n|\n)/g, '$1\t'); - - const maxKeyLength = Math.max( - ...Object.keys({ [key]: value }).map((k) => k.length), - ); - const padding = ' '.repeat(maxKeyLength - key.length); - const expectedOutput = `${expectedKey}${padding}\t${expectedValue}\n`; - // Assert that the formatted output matches the expected output - expect(formattedOutput).toBe(expectedOutput); - }, - ), - { numRuns: 100 }, // Number of times to run the test + test.prop([stringWithNonPrintableCharsArb, stringWithNonPrintableCharsArb], { + numRuns: 100, + })('should encode non-printable characters within a dict', (key, value) => { + const formattedOutput = binUtils.outputFormatter({ + type: 'dict', + data: { [key]: value }, + }); + const expectedKey = binUtils.encodeEscapedWrapped(key); + // Construct the expected output + let expectedValue = value; + expectedValue = binUtils.encodeEscapedWrapped(expectedValue); + expectedValue = expectedValue.replace(/(?:\r\n|\n)$/, ''); + expectedValue = expectedValue.replace(/(\r\n|\n)/g, '$1\t'); + const maxKeyLength = Math.max( + ...Object.keys({ [key]: value }).map((k) => k.length), ); + const padding = ' '.repeat(maxKeyLength - key.length); + const expectedOutput = `${expectedKey}${padding}\t${expectedValue}\n`; + // Assert that the formatted output matches the expected output + expect(formattedOutput).toBe(expectedOutput); }); test('errors in human and json format', () => { const nodeIdGenerator = ids.createNodeIdGenerator(); @@ -302,43 +295,87 @@ describe('bin/utils', () => { '\n', ); }); - test('encodeEscaped should encode all escapable characters', () => { - fc.assert( - fc.property(stringWithNonPrintableCharsArb, (value) => { - expect(binUtils.decodeEscaped(binUtils.encodeEscaped(value))).toBe( - value, - ); - }), - { numRuns: 100 }, // Number of times to run the test - ); - }); - test('encodeEscapedReplacer should encode all escapable characters', () => { - fc.assert( - fc.property( - stringWithNonPrintableCharsArb, - stringWithNonPrintableCharsArb, - (key, value) => { - const encodedKey = binUtils.encodeEscaped(key); - const encodedValue = binUtils.encodeEscaped(value); - const object = { - [key]: value, - [key]: { - [key]: value, - }, - [key]: [value], - }; - const encodedObject = { - [encodedKey]: encodedValue, - [encodedKey]: { - [encodedKey]: encodedValue, - }, - [encodedKey]: [encodedValue], - }; - const output = JSON.stringify(object, binUtils.encodeEscapedReplacer); - expect(JSON.parse(output)).toEqual(encodedObject); + test.prop([stringWithNonPrintableCharsArb], { numRuns: 100 })( + 'encodeEscaped should encode all escapable characters', + (value) => { + expect(binUtils.decodeEscaped(binUtils.encodeEscaped(value))).toBe(value); + }, + ); + test.prop([stringWithNonPrintableCharsArb, stringWithNonPrintableCharsArb], { + numRuns: 100, + })( + 'encodeEscapedReplacer should encode all escapable characters', + (key, value) => { + const encodedKey = binUtils.encodeEscaped(key); + const encodedValue = binUtils.encodeEscaped(value); + const object = { + [key]: value, + [key]: { + [key]: value, }, - ), - { numRuns: 100 }, // Number of times to run the test - ); - }); + [key]: [value], + }; + const encodedObject = { + [encodedKey]: encodedValue, + [encodedKey]: { + [encodedKey]: encodedValue, + }, + [encodedKey]: [encodedValue], + }; + const output = JSON.stringify(object, binUtils.encodeEscapedReplacer); + expect(JSON.parse(output)).toEqual(encodedObject); + }, + ); +}); + +describe('parsers', () => { + const vaultNameArb = fc.stringOf( + fc.char().filter((c) => binParsers.vaultNameRegex.test(c)), + { minLength: 1, maxLength: 100 }, + ); + const singleSecretPathArb = fc.stringOf( + fc.char().filter((c) => binParsers.secretPathRegex.test(c)), + { minLength: 1, maxLength: 25 }, + ); + const secretPathArb = fc + .array(singleSecretPathArb, { minLength: 1, maxLength: 5 }) + .map((segments) => path.join(...segments)); + const valueFirstCharArb = fc.char().filter((c) => /^[a-zA-Z_]$/.test(c)); + const valueRestCharArb = fc.stringOf( + fc.char().filter((c) => /^[\w]$/.test(c)), + { minLength: 1, maxLength: 100 }, + ); + const valueDataArb = fc + .tuple(valueFirstCharArb, valueRestCharArb) + .map((components) => components.join('')); + + test.prop([vaultNameArb], { numRuns: 100 })( + 'should parse vault name', + async (vaultName) => { + expect(binParsers.parseVaultName(vaultName)).toEqual(vaultName); + }, + ); + test.prop([vaultNameArb], { numRuns: 10 })( + 'should parse secret path with only vault name', + async (vaultName) => { + const result = [vaultName, undefined, undefined]; + expect(binParsers.parseSecretPath(vaultName)).toEqual(result); + }, + ); + test.prop([vaultNameArb, secretPathArb], { numRuns: 100 })( + 'should parse full secret path with vault name', + async (vaultName, secretPath) => { + const query = `${vaultName}:${secretPath}`; + const result = [vaultName, secretPath, undefined]; + expect(binParsers.parseSecretPath(query)).toEqual(result); + }, + ); + test.prop([vaultNameArb, secretPathArb, valueDataArb], { numRuns: 100 })( + 'should parse full secret path with vault name and value', + async (vaultName, secretPath, valueData) => { + const query = `${vaultName}:${secretPath}=${valueData}`; + const result = [vaultName, secretPath, valueData]; + expect(binParsers.parseSecretPathValue(query)).toEqual(result); + }, + ); });