Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions tests/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,90 @@ describe('project configuration', () => {
);
}));

it('rejects secret-bearing fields without echoing their values', () =>
it('rejects unknown top-level fields with generic non-reflective error', () =>
withTempDirectory((root) => {
const secret = 'S-DO-NOT-ECHO-THIS-VALUE';
writeFileSync(
join(root, PROJECT_CONFIG_FILENAME),
JSON.stringify({
version: 1,
privateKey: secret,
}),
JSON.stringify({ version: 1, unknownField: 'value' }),
);
expect(() => loadProjectConfig(root)).toThrow(ValidationCliError);
try {
loadProjectConfig(root);
} catch (e) {
expect((e as Error).message).not.toContain('unknownField');
expect((e as Error).message).not.toContain('value');
}
}));

it('rejects nested/array unexpected values safely', () =>
withTempDirectory((root) => {
writeFileSync(
join(root, PROJECT_CONFIG_FILENAME),
JSON.stringify({ version: 1, nested: { bad: 'x' } }),
);
expect(() => loadProjectConfig(root)).toThrow(ValidationCliError);
}));

it('does not echo secret-looking field values in errors', () =>
withTempDirectory((root) => {
writeFileSync(
join(root, PROJECT_CONFIG_FILENAME),
JSON.stringify({ version: 1, password: 's3cr3t' }),
);
try {
loadProjectConfig(root);
} catch (e) {
expect((e as Error).message).not.toContain('s3cr3t');
}
}));

it('schema version remains unchanged (version 1)', () =>
withTempDirectory((root) => {
writeFileSync(
join(root, PROJECT_CONFIG_FILENAME),
JSON.stringify({ version: 1, network: 'testnet' }),
);
const cfg = loadProjectConfig(root);
expect(cfg?.version).toBe(1);
}));

it('rejects project-config larger than 64 KiB with predictable error and no content echo', () =>
withTempDirectory((root) => {
const large = 'x'.repeat(64 * 1024 + 1);
writeFileSync(
join(root, PROJECT_CONFIG_FILENAME),
JSON.stringify({ version: 1, extra: large }),
);
try {
loadProjectConfig(root);
throw new Error('Expected configuration validation to fail.');
} catch (error) {
expect(error).toBeInstanceOf(ValidationCliError);
expect((error as Error).message).toContain(
'must not contain secret material',
);
expect((error as Error).message).not.toContain(secret);
throw new Error('Expected failure');
} catch (e) {
expect(e).toBeInstanceOf(ValidationCliError);
expect((e as Error).message).toContain('64 KiB');
expect((e as Error).message).not.toContain(large);
}
}));

it.each([64 * 1024 - 1, 64 * 1024])(
'does not reject a configuration of exactly %i bytes for size',
(totalBytes) =>
withTempDirectory((root) => {
const base = JSON.stringify({ version: 1, padding: '' });
const padding = 'x'.repeat(totalBytes - base.length);
const content = JSON.stringify({ version: 1, padding });
expect(Buffer.byteLength(content)).toBe(totalBytes);

writeFileSync(join(root, PROJECT_CONFIG_FILENAME), content);

try {
loadProjectConfig(root);
} catch (error) {
expect((error as Error).message).not.toContain('64 KiB');
expect((error as Error).message).toContain('unsupported field');
}
}),
);

it.each([
'SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
Expand Down
31 changes: 31 additions & 0 deletions tests/project-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ describe('project input validation', () => {
expect(validated.cwd).toBe(resolve('./workspace/..'));
},
);

it.each([
' ',
'\t',
'\n',
'\r\n',
'\u00A0',
'\u200B',
'\u0000',
'de mo',
'de\tmo',
'de\nmo',
'demo\u0000',
'demo\u200B',
])('rejects ambiguous whitespace/control input %j', (projectName) => {
expect(() =>
validateProjectInput({ projectName, cwd: '/workspace' }),
).toThrow(ValidationCliError);
});

it.each([' demo ', '\tdemo', 'demo\n', 'demo\u00A0'])(
'normalizes surrounding whitespace in %j to a single segment',
(projectName) => {
const validated = validateProjectInput({
projectName,
cwd: '/workspace',
});

expect(validated.projectName).toBe('demo');
},
);
});

describe('project destination planning', () => {
Expand Down
15 changes: 15 additions & 0 deletions tests/root-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,19 @@ describe('StellarForge root command', () => {
expect(help).toContain('-V, --version');
expect(help).toContain('-h, --help');
});

it('exposes exactly the implemented v1 command inventory in global help', () => {
const help = createRootCommand('1.2.3').helpInformation();

const implemented = ['doctor', 'new', 'dev', 'test', 'deploy'];
for (const cmd of implemented) {
expect(help).toContain(cmd);
}

const notAdvertised = ['add', 'plugin', 'remote-template'];
for (const cmd of notAdvertised) {
expect(help).not.toContain(`add command`);
expect(help).not.toMatch(new RegExp(`\\b${cmd}\\b`));
}
});
});