open-vault/ui/tests/unit/lib/console-helpers-test.js

510 lines
14 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
import { module, test } from 'qunit';
import {
parseCommand,
logFromResponse,
logFromError,
formattedErrorFromInput,
extractFlagsFromStrings,
extractDataFromStrings,
} from 'vault/lib/console-helpers';
module('Unit | Lib | console helpers', function () {
const testCommands = [
{
name: 'write with data',
command: `vault write aws/config/root \
access_key=AKIAJWVN5Z4FOFT7NLNA \
secret_key=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 \
region=us-east-1`,
expected: {
method: 'write',
flagArray: [],
path: 'aws/config/root',
dataArray: [
'access_key=AKIAJWVN5Z4FOFT7NLNA',
'secret_key=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',
'region=us-east-1',
],
},
},
{
name: 'write with space in a value',
command: `vault write \
auth/ldap/config \
url=ldap://ldap.example.com:3268 \
binddn="CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com" \
bindpass="xxxxxxxxxxxxxxxxxxxxxxxxxx" \
userdn="DC=example,DC=com" \
groupdn="DC=example,DC=com" \
insecure_tls=true \
starttls=false
`,
expected: {
method: 'write',
flagArray: [],
path: 'auth/ldap/config',
dataArray: [
'url=ldap://ldap.example.com:3268',
'binddn=CN=ServiceViewDev,OU=Service Accounts,DC=example,DC=com',
'bindpass=xxxxxxxxxxxxxxxxxxxxxxxxxx',
'userdn=DC=example,DC=com',
'groupdn=DC=example,DC=com',
'insecure_tls=true',
'starttls=false',
],
},
},
{
name: 'write with double quotes',
command: `vault write \
auth/token/create \
policies="foo"
`,
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
},
{
name: 'write with single quotes',
command: `vault write \
auth/token/create \
policies='foo'
`,
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ['policies=foo'] },
},
{
name: 'write with unmatched quotes',
command: `vault write \
auth/token/create \
policies="'foo"
`,
expected: { method: 'write', flagArray: [], path: 'auth/token/create', dataArray: ["policies='foo"] },
},
{
name: 'write with shell characters',
/* eslint-disable no-useless-escape */
command: `vault write database/roles/api-prod db_name=apiprod creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" default_ttl=1h max_ttl=24h
`,
expected: {
method: 'write',
flagArray: [],
path: 'database/roles/api-prod',
dataArray: [
'db_name=apiprod',
`creation_statements=CREATE ROLE {{name}} WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO {{name}};`,
'default_ttl=1h',
'max_ttl=24h',
],
},
},
{
name: 'read with field',
command: `vault read -field=access_key aws/creds/my-role`,
expected: {
method: 'read',
flagArray: ['-field=access_key'],
path: 'aws/creds/my-role',
dataArray: [],
},
},
];
testCommands.forEach(function (testCase) {
test(`#parseCommand: ${testCase.name}`, function (assert) {
const result = parseCommand(testCase.command);
assert.deepEqual(result, testCase.expected);
});
});
test('#parseCommand: invalid commands', function (assert) {
assert.expect(1);
const command = 'vault kv get foo';
assert.throws(
() => {
parseCommand(command);
},
/invalid command/,
'throws on invalid command'
);
});
const testExtractCases = [
{
method: 'read',
name: 'data fields',
dataInput: [
'access_key=AKIAJWVN5Z4FOFT7NLNA',
'secret_key=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',
'region=us-east-1',
],
flagInput: [],
expected: {
data: {
access_key: 'AKIAJWVN5Z4FOFT7NLNA',
secret_key: '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',
region: 'us-east-1',
},
flags: {},
},
},
{
method: 'read',
name: 'repeated data and a flag',
dataInput: ['allowed_domains=example.com', 'allowed_domains=foo.example.com'],
flagInput: ['-wrap-ttl=2h'],
expected: {
data: {
allowed_domains: ['example.com', 'foo.example.com'],
},
flags: {
wrapTTL: '2h',
},
},
},
{
method: 'read',
name: 'triple data',
dataInput: [
'allowed_domains=example.com',
'allowed_domains=foo.example.com',
'allowed_domains=dev.example.com',
],
flagInput: [],
expected: {
data: {
allowed_domains: ['example.com', 'foo.example.com', 'dev.example.com'],
},
flags: {},
},
},
{
method: 'read',
name: 'data with more than one equals sign',
dataInput: ['foo=bar=baz', 'foo=baz=bop', 'some=value=val'],
flagInput: [],
expected: {
data: {
foo: ['bar=baz', 'baz=bop'],
some: 'value=val',
},
flags: {},
},
},
{
method: 'read',
name: 'data with empty values',
dataInput: [`foo=`, 'some=thing'],
flagInput: [],
expected: {
data: {
foo: '',
some: 'thing',
},
flags: {},
},
},
{
method: 'write',
name: 'write with force flag',
dataInput: [],
flagInput: ['-force'],
expected: {
data: {},
flags: {
force: true,
},
},
},
{
method: 'write',
name: 'write with force short flag',
dataInput: [],
flagInput: ['-f'],
expected: {
data: {},
flags: {
force: true,
},
},
},
{
method: 'write',
name: 'write with GNU style force flag',
dataInput: [],
flagInput: ['--force'],
expected: {
data: {},
flags: {
force: true,
},
},
},
];
testExtractCases.forEach(function (testCase) {
test(`#extractDataFromStrings: ${testCase.name}`, function (assert) {
const data = extractDataFromStrings(testCase.dataInput);
assert.deepEqual(data, testCase.expected.data, 'has expected data');
});
test(`#extractFlagsFromStrings: ${testCase.name}`, function (assert) {
const flags = extractFlagsFromStrings(testCase.flagInput, testCase.method);
assert.deepEqual(flags, testCase.expected.flags, 'has expected flags');
});
});
const testResponseCases = [
{
name: 'write response, no content',
args: [null, 'foo/bar', 'write', {}],
expectedData: {
type: 'success',
content: 'Success! Data written to: foo/bar',
},
},
{
name: 'delete response, no content',
args: [null, 'foo/bar', 'delete', {}],
expectedData: {
type: 'success',
content: 'Success! Data deleted (if it existed) at: foo/bar',
},
},
{
name: 'read, no data, auth, wrap_info',
args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', {}],
expectedData: {
type: 'object',
content: { foo: 'bar', one: 'two' },
},
},
{
name: 'read with -format=json flag, no data, auth, wrap_info',
args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', { format: 'json' }],
expectedData: {
type: 'json',
content: { foo: 'bar', one: 'two' },
},
},
{
name: 'read with -field flag, no data, auth, wrap_info',
args: [{ foo: 'bar', one: 'two' }, 'foo/bar', 'read', { field: 'one' }],
expectedData: {
type: 'text',
content: 'two',
},
},
{
name: 'write, with content',
args: [{ data: { one: 'two' } }, 'foo/bar', 'write', {}],
expectedData: {
type: 'object',
content: { one: 'two' },
},
},
{
name: 'with wrap-ttl flag',
args: [{ wrap_info: { one: 'two' } }, 'foo/bar', 'read', { wrapTTL: '1h' }],
expectedData: {
type: 'object',
content: { one: 'two' },
},
},
{
name: 'with -format=json flag and wrap-ttl flag',
args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', wrapTTL: '1h' }],
expectedData: {
type: 'json',
content: { foo: 'bar', wrap_info: { one: 'two' } },
},
},
{
name: 'with -format=json and -field flags',
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { format: 'json', field: 'one' }],
expectedData: {
type: 'json',
content: 'two',
},
},
{
name: 'with -format=json and -field, and -wrap-ttl flags',
args: [
{ foo: 'bar', wrap_info: { one: 'two' } },
'foo/bar',
'read',
{ format: 'json', wrapTTL: '1h', field: 'one' },
],
expectedData: {
type: 'json',
content: 'two',
},
},
{
name: 'with string field flag and wrap-ttl flag',
args: [{ foo: 'bar', wrap_info: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
expectedData: {
type: 'text',
content: 'two',
},
},
{
name: 'with object field flag and wrap-ttl flag',
args: [
{ foo: 'bar', wrap_info: { one: { two: 'three' } } },
'foo/bar',
'read',
{ field: 'one', wrapTTL: '1h' },
],
expectedData: {
type: 'object',
content: { two: 'three' },
},
},
{
name: 'with response data and string field flag',
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'one', wrapTTL: '1h' }],
expectedData: {
type: 'text',
content: 'two',
},
},
{
name: 'with response data and object field flag ',
args: [
{ foo: 'bar', data: { one: { two: 'three' } } },
'foo/bar',
'read',
{ field: 'one', wrapTTL: '1h' },
],
expectedData: {
type: 'object',
content: { two: 'three' },
},
},
{
name: 'response with data',
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', {}],
expectedData: {
type: 'object',
content: { one: 'two' },
},
},
{
name: 'with response data, field flag, and field missing',
args: [{ foo: 'bar', data: { one: 'two' } }, 'foo/bar', 'read', { field: 'foo' }],
expectedData: {
type: 'error',
content: 'Field "foo" not present in secret',
},
},
{
name: 'with response data and auth block',
args: [{ data: { one: 'two' }, auth: { three: 'four' } }, 'auth/token/create', 'write', {}],
expectedData: {
type: 'object',
content: { three: 'four' },
},
},
{
name: 'with -field and -format with an object field',
args: [{ data: { one: { three: 'two' } } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
expectedData: {
type: 'json',
content: { three: 'two' },
},
},
{
name: 'with -field and -format with a string field',
args: [{ data: { one: 'two' } }, 'sys/mounts', 'read', { field: 'one', format: 'json' }],
expectedData: {
type: 'json',
content: 'two',
},
},
];
testResponseCases.forEach(function (testCase) {
test(`#logFromResponse: ${testCase.name}`, function (assert) {
const data = logFromResponse(...testCase.args);
assert.deepEqual(data, testCase.expectedData);
});
});
const testErrorCases = [
{
name: 'AdapterError write',
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'write'],
expectedContent: 'Error writing to: sys/foo.\nURL: v1/sys/foo\nCode: 404',
},
{
name: 'AdapterError read',
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'read'],
expectedContent: 'Error reading from: sys/foo.\nURL: v1/sys/foo\nCode: 404',
},
{
name: 'AdapterError list',
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'list'],
expectedContent: 'Error listing: sys/foo.\nURL: v1/sys/foo\nCode: 404',
},
{
name: 'AdapterError delete',
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: [{}] }, 'sys/foo', 'delete'],
expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404',
},
{
name: 'VaultError single error',
args: [{ httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token'] }, 'sys/foo', 'delete'],
expectedContent: 'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token',
},
{
name: 'VaultErrors multiple errors',
args: [
{ httpStatus: 404, path: 'v1/sys/foo', errors: ['no client token', 'this is an error'] },
'sys/foo',
'delete',
],
expectedContent:
'Error deleting at: sys/foo.\nURL: v1/sys/foo\nCode: 404\nErrors:\n no client token\n this is an error',
},
];
testErrorCases.forEach(function (testCase) {
test(`#logFromError: ${testCase.name}`, function (assert) {
const data = logFromError(...testCase.args);
assert.deepEqual(
data,
{ type: 'error', content: testCase.expectedContent },
'returns the expected data'
);
});
});
const testCommandCases = [
{
name: 'errors when command does not include a path',
args: [],
expectedContent: 'A path is required to make a request.',
},
{
name: 'errors when write command does not include data and does not have force tag',
args: ['foo/bar', 'write', {}, []],
expectedContent: 'Must supply data or use -force',
},
];
testCommandCases.forEach(function (testCase) {
test(`#formattedErrorFromInput: ${testCase.name}`, function (assert) {
const data = formattedErrorFromInput(...testCase.args);
assert.deepEqual(
data,
{ type: 'error', content: testCase.expectedContent },
'returns the pcorrect data'
);
});
});
});