Skip to content

Commit 25ed285

Browse files
fix: reject blank project passwords (#139)
1 parent 495db2f commit 25ed285

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
@@ -637,6 +637,33 @@ describe('runCreate', () => {
637637
).rejects.toMatchObject({ exitCode: 5, code: 'VALIDATION_ERROR' });
638638
expect(fetchImpl).not.toHaveBeenCalled();
639639
});
640+
it('rejects a whitespace-only --password with VALIDATION_ERROR (exit 5), no network', async () => {
641+
const { credentialsPath } = makeCreds();
642+
const fetchImpl = vi.fn(async () => {
643+
throw new Error('should not hit network - validation must fire client-side');
644+
});
645+
646+
await expect(
647+
runCreate(
648+
{
649+
profile: 'default',
650+
output: 'json',
651+
debug: false,
652+
type: 'frontend',
653+
name: 'Password Guard Project',
654+
targetUrl: 'https://example.com',
655+
password: ' ',
656+
},
657+
{
658+
credentialsPath,
659+
fetchImpl: fetchImpl as unknown as typeof fetch,
660+
stdout: () => {},
661+
stderr: () => {},
662+
},
663+
),
664+
).rejects.toMatchObject({ exitCode: 5, code: 'VALIDATION_ERROR' });
665+
expect(fetchImpl).not.toHaveBeenCalled();
666+
});
640667
});
641668

642669
// ---------------------------------------------------------------------------
@@ -739,6 +766,30 @@ describe('runUpdate', () => {
739766
expect(fetchImpl).not.toHaveBeenCalled();
740767
});
741768

769+
it('rejects a whitespace-only --password with VALIDATION_ERROR (exit 5), no network', async () => {
770+
const { credentialsPath } = makeCreds();
771+
const fetchImpl = vi.fn(async () => {
772+
throw new Error('should not be called');
773+
});
774+
await expect(
775+
runUpdate(
776+
{
777+
profile: 'default',
778+
output: 'json',
779+
debug: false,
780+
projectId: 'proj_abc',
781+
password: ' ',
782+
},
783+
{
784+
credentialsPath,
785+
fetchImpl: fetchImpl as unknown as typeof fetch,
786+
stdout: () => {},
787+
stderr: () => {},
788+
},
789+
),
790+
).rejects.toMatchObject({ code: 'VALIDATION_ERROR', exitCode: 5 });
791+
expect(fetchImpl).not.toHaveBeenCalled();
792+
});
742793
it('P7 — dry-run returns canned shape without network call', async () => {
743794
resetDryRunBannerForTesting();
744795
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)