Skip to content

Commit e0d3f3e

Browse files
fix: reject blank project passwords
1 parent e53257d commit e0d3f3e

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

src/commands/project.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,33 @@ describe('runCreate', () => {
596596
).rejects.toMatchObject({ exitCode: 5, code: 'VALIDATION_ERROR' });
597597
expect(fetchImpl).not.toHaveBeenCalled();
598598
});
599+
it('rejects a whitespace-only --password with VALIDATION_ERROR (exit 5), no network', async () => {
600+
const { credentialsPath } = makeCreds();
601+
const fetchImpl = vi.fn(async () => {
602+
throw new Error('should not hit network - validation must fire client-side');
603+
});
604+
605+
await expect(
606+
runCreate(
607+
{
608+
profile: 'default',
609+
output: 'json',
610+
debug: false,
611+
type: 'frontend',
612+
name: 'Password Guard Project',
613+
targetUrl: 'https://example.com',
614+
password: ' ',
615+
},
616+
{
617+
credentialsPath,
618+
fetchImpl: fetchImpl as unknown as typeof fetch,
619+
stdout: () => {},
620+
stderr: () => {},
621+
},
622+
),
623+
).rejects.toMatchObject({ exitCode: 5, code: 'VALIDATION_ERROR' });
624+
expect(fetchImpl).not.toHaveBeenCalled();
625+
});
599626
});
600627

601628
// ---------------------------------------------------------------------------
@@ -698,6 +725,30 @@ describe('runUpdate', () => {
698725
expect(fetchImpl).not.toHaveBeenCalled();
699726
});
700727

728+
it('rejects a whitespace-only --password with VALIDATION_ERROR (exit 5), no network', async () => {
729+
const { credentialsPath } = makeCreds();
730+
const fetchImpl = vi.fn(async () => {
731+
throw new Error('should not be called');
732+
});
733+
await expect(
734+
runUpdate(
735+
{
736+
profile: 'default',
737+
output: 'json',
738+
debug: false,
739+
projectId: 'proj_abc',
740+
password: ' ',
741+
},
742+
{
743+
credentialsPath,
744+
fetchImpl: fetchImpl as unknown as typeof fetch,
745+
stdout: () => {},
746+
stderr: () => {},
747+
},
748+
),
749+
).rejects.toMatchObject({ code: 'VALIDATION_ERROR', exitCode: 5 });
750+
expect(fetchImpl).not.toHaveBeenCalled();
751+
});
701752
it('P7 — dry-run returns canned shape without network call', async () => {
702753
resetDryRunBannerForTesting();
703754
const { credentialsPath } = makeCreds();

src/commands/project.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ export async function runCreate(
150150
if (opts.name !== undefined && opts.name.trim().length === 0) {
151151
throw localValidationError('--name must not be empty or whitespace-only');
152152
}
153+
if (opts.password !== undefined && opts.password.trim().length === 0) {
154+
throw localValidationError('--password must not be empty or whitespace-only');
155+
}
153156

154157
// P1-3: client-side length checks matching server limits.
155158
if (opts.name !== undefined && opts.name.length > 200) {
@@ -264,6 +267,9 @@ export async function runUpdate(
264267
if (opts.name !== undefined && opts.name.trim().length === 0) {
265268
throw localValidationError('--name must not be empty or whitespace-only');
266269
}
270+
if (opts.password !== undefined && opts.password.trim().length === 0) {
271+
throw localValidationError('--password must not be empty or whitespace-only');
272+
}
267273
if (opts.name !== undefined && opts.name.length > 200) {
268274
throw localValidationError('--name must be at most 200 characters');
269275
}

0 commit comments

Comments
 (0)