Skip to content

Commit 4e915f8

Browse files
committed
chore: writing fastcheck tests for parsers
1 parent 38275c8 commit 4e915f8

File tree

2 files changed

+76
-28
lines changed

2 files changed

+76
-28
lines changed

src/utils/parsers.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import * as gestaltsUtils from 'polykey/dist/gestalts/utils';
88
import * as networkUtils from 'polykey/dist/network/utils';
99
import * as nodesUtils from 'polykey/dist/nodes/utils';
1010

11-
const vaultNameRegex = /^([\w-.]+)$/;
12-
const secretPathRegex = /^([^\0\\=]+)?$/;
11+
const vaultNameRegex = /^([\w\-.]+)$/;
12+
const secretPathRegex = /^([\w\/;,.]+)?$/;
1313
const secretPathValueRegex = /^([a-zA-Z_][\w]+)?$/;
1414
const environmentVariableRegex = /^([a-zA-Z_]+[a-zA-Z0-9_]*)?$/;
1515

@@ -95,24 +95,20 @@ function parseSecretPath(secretPath: string): [string, string?, string?] {
9595
// The colon character `:` is prohibited in vaultName, so it's first occurence
9696
// means that this is the delimiter between vaultName and secretPath.
9797
const colonIndex = splitSecretPath.indexOf(':');
98+
// If no colon exists, treat entire string as vault name
99+
if (colonIndex === -1) {
100+
return [parseVaultName(splitSecretPath), undefined, value];
101+
}
98102
// Calculate contents before the `=` separator
99-
const vaultNamePart =
100-
colonIndex === -1
101-
? splitSecretPath
102-
: splitSecretPath.substring(0, colonIndex);
103-
const secretPathPart =
104-
colonIndex === -1 ? undefined : splitSecretPath.substring(colonIndex + 1);
105-
106-
if (secretPathPart && !secretPathRegex.test(secretPathPart)) {
103+
const vaultNamePart = splitSecretPath.substring(0, colonIndex);
104+
const secretPathPart = splitSecretPath.substring(colonIndex + 1);
105+
if (secretPathPart != null && !secretPathRegex.test(secretPathPart)) {
107106
throw new commander.InvalidArgumentError(
108107
`${secretPath} is not of the format <vaultName>[:<secretPath>][=<value>]`,
109108
);
110109
}
111110
const parsedVaultName = parseVaultName(vaultNamePart);
112-
const parsedSecretPath =
113-
secretPathPart == null
114-
? undefined
115-
: secretPathPart.match(secretPathRegex)![1];
111+
const parsedSecretPath = secretPathPart.match(secretPathRegex)?.[1];
116112
return [parsedVaultName, parsedSecretPath, value];
117113
}
118114

tests/utils.test.ts

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import type { Host, Port } from 'polykey/dist/network/types';
22
import ErrorPolykey from 'polykey/dist/ErrorPolykey';
3+
import { test } from '@fast-check/jest';
34
import * as ids from 'polykey/dist/ids';
45
import * as nodesUtils from 'polykey/dist/nodes/utils';
56
import * as polykeyErrors from 'polykey/dist/errors';
67
import * as fc from 'fast-check';
78
import * as binUtils from '@/utils/utils';
9+
import * as binParsers from '@/utils/parsers';
10+
import path from 'path';
811

9-
const nonPrintableCharArb = fc
10-
.oneof(
11-
fc.integer({ min: 0, max: 0x1f }),
12-
fc.integer({ min: 0x7f, max: 0x9f }),
13-
)
14-
.map((code) => String.fromCharCode(code));
12+
describe('outputFormatters', () => {
13+
const nonPrintableCharArb = fc
14+
.oneof(
15+
fc.integer({ min: 0, max: 0x1f }),
16+
fc.integer({ min: 0x7f, max: 0x9f }),
17+
)
18+
.map((code) => String.fromCharCode(code));
1519

16-
const stringWithNonPrintableCharsArb = fc.stringOf(
17-
fc.oneof(fc.char(), nonPrintableCharArb),
18-
);
20+
const stringWithNonPrintableCharsArb = fc.stringOf(
21+
fc.oneof(fc.char(), nonPrintableCharArb),
22+
);
1923

20-
describe('bin/utils', () => {
2124
test('list in human and json format', () => {
2225
// List
2326
expect(
@@ -164,7 +167,7 @@ describe('bin/utils', () => {
164167
' key9\tvalue\n',
165168
);
166169
});
167-
test('outputFormatter should encode non-printable characters within a dict', () => {
170+
test('should encode non-printable characters within a dict', () => {
168171
fc.assert(
169172
fc.property(
170173
stringWithNonPrintableCharsArb,
@@ -174,15 +177,12 @@ describe('bin/utils', () => {
174177
type: 'dict',
175178
data: { [key]: value },
176179
});
177-
178180
const expectedKey = binUtils.encodeEscapedWrapped(key);
179-
180181
// Construct the expected output
181182
let expectedValue = value;
182183
expectedValue = binUtils.encodeEscapedWrapped(expectedValue);
183184
expectedValue = expectedValue.replace(/(?:\r\n|\n)$/, '');
184185
expectedValue = expectedValue.replace(/(\r\n|\n)/g, '$1\t');
185-
186186
const maxKeyLength = Math.max(
187187
...Object.keys({ [key]: value }).map((k) => k.length),
188188
);
@@ -342,3 +342,55 @@ describe('bin/utils', () => {
342342
);
343343
});
344344
});
345+
346+
describe('parsers', () => {
347+
const vaultNameArb = fc.stringOf(
348+
fc.char().filter((c) => binParsers.vaultNameRegex.test(c)),
349+
{ minLength: 1, maxLength: 100 },
350+
);
351+
const singleSecretPathArb = fc.stringOf(
352+
fc.char().filter((c) => binParsers.secretPathRegex.test(c)),
353+
{ minLength: 1, maxLength: 25 },
354+
);
355+
const secretPathArb = fc
356+
.array(singleSecretPathArb, { minLength: 1, maxLength: 5 })
357+
.map((segments) => path.join(...segments));
358+
const valueFirstCharArb = fc.char().filter((c) => /^[a-zA-Z_]$/.test(c));
359+
const valueRestCharArb = fc.stringOf(
360+
fc.char().filter((c) => /^[\w]$/.test(c)),
361+
{ minLength: 1, maxLength: 100 },
362+
);
363+
const valueDataArb = fc
364+
.tuple(valueFirstCharArb, valueRestCharArb)
365+
.map((components) => components.join(''));
366+
367+
test.prop([vaultNameArb], { numRuns: 100 })(
368+
'should parse vault name',
369+
async (vaultName) => {
370+
expect(binParsers.parseVaultName(vaultName)).toEqual(vaultName);
371+
},
372+
);
373+
test.prop([vaultNameArb], { numRuns: 10 })(
374+
'should parse secret path with only vault name',
375+
async (vaultName) => {
376+
const result = [vaultName, undefined, undefined];
377+
expect(binParsers.parseSecretPath(vaultName)).toEqual(result);
378+
},
379+
);
380+
test.prop([vaultNameArb, secretPathArb], { numRuns: 100 })(
381+
'should parse full secret path with vault name',
382+
async (vaultName, secretPath) => {
383+
const query = `${vaultName}:${secretPath}`;
384+
const result = [vaultName, secretPath, undefined];
385+
expect(binParsers.parseSecretPath(query)).toEqual(result);
386+
},
387+
);
388+
test.prop([vaultNameArb, secretPathArb, valueDataArb], { numRuns: 100 })(
389+
'should parse full secret path with vault name and value',
390+
async (vaultName, secretPath, valueData) => {
391+
const query = `${vaultName}:${secretPath}=${valueData}`;
392+
const result = [vaultName, secretPath, valueData];
393+
expect(binParsers.parseSecretPathValue(query)).toEqual(result);
394+
},
395+
);
396+
});

0 commit comments

Comments
 (0)