MCPcopy Create free account
hub / github.com/TestSprite/testsprite-cli / runCreate

Function runCreate

src/commands/test.ts:638–834  ·  view source on GitHub ↗
(
  opts: CreateOptions,
  deps: TestDeps = {},
)

Source from the content-addressed store, hash-verified

636 * convention; no separate "envelope-only" output mode.
637 */
638export async function runCreate(
639 opts: CreateOptions,
640 deps: TestDeps = {},
641): Promise<CliCreateTestResponse> {
642 // P1-2: validate idempotency key before any I/O — non-ASCII chars cause a
643 // ByteString TypeError at the HTTP transport layer (exit 10 UNAVAILABLE).
644 assertIdempotencyKey(opts.idempotencyKey);
645 // codex #128 P2: the `--run` chain derives `<key>:run` (see below); validate
646 // that derived key BEFORE the create POST so a near-limit base key fails fast
647 // (exit 5) instead of creating the test and THEN rejecting the 257+ char run
648 // key — which would orphan a created test with no run.
649 assertChainedRunKeyFits(opts.run, opts.idempotencyKey);
650 // Validate inputs before touching credentials or fs — matches the
651 // M2 read commands' "input gates first, then auth, then I/O" ordering.
652 requireProjectId(opts.projectId);
653 requireNonEmpty('name', opts.name);
654 // P1-3: client-side length checks matching server limits (name ≤200,
655 // description ≤2000) so the user gets instant, actionable errors instead
656 // of a cryptic server validation message.
657 if (opts.name !== undefined && opts.name.length > 200) {
658 throw localValidationError('name', 'must be at most 200 characters');
659 }
660 if (opts.description !== undefined && opts.description.length > 2000) {
661 throw localValidationError('description', 'must be at most 2000 characters');
662 }
663 // P2-11: extend the required-flag error message to suggest --plan-from for
664 // FE tests so operators who missed that flag get an actionable hint.
665 if (typeof opts.codeFile !== 'string' || opts.codeFile.length === 0) {
666 throw ApiError.fromEnvelope({
667 error: {
668 code: 'VALIDATION_ERROR',
669 message: 'Invalid request.',
670 nextAction:
671 'Flag `--code-file` is invalid: is required. ' +
672 'For frontend tests you can also supply the plan with `--plan-from <plan.json>` instead of a code file.',
673 requestId: 'local',
674 details: { field: 'codeFile', reason: 'is required' },
675 },
676 });
677 }
678 if (!['frontend', 'backend'].includes(opts.type)) {
679 throw localValidationError('type', 'must be one of: frontend, backend', [
680 'frontend',
681 'backend',
682 ]);
683 }
684 if (opts.priority !== undefined && !CLI_CREATE_PRIORITIES.includes(opts.priority)) {
685 throw localValidationError('priority', `must be one of: ${CLI_CREATE_PRIORITIES.join(', ')}`, [
686 ...CLI_CREATE_PRIORITIES,
687 ]);
688 }
689
690 // M4 piece-2: --produces/--needs/--category are backend-only. FE plans have
691 // no wave model; fail-fast client-side to match the backend's 400 reject and
692 // save a round-trip.
693 if (opts.type === 'frontend') {
694 const depFlags: string[] = [];
695 if (opts.produces !== undefined && opts.produces.length > 0) depFlags.push('--produces');

Callers 3

test.test.tsFile · 0.70
createTestCommandFunction · 0.70
project.test.tsFile · 0.70

Calls 15

assertIdempotencyKeyFunction · 0.85
assertChainedRunKeyFitsFunction · 0.85
requireProjectIdFunction · 0.85
requireNonEmptyFunction · 0.85
readCodeFileGuardedFunction · 0.85
stderrFunction · 0.85
assertNotLocalFunction · 0.85
resolvePortalUrlFunction · 0.85
resolveApiUrlFunction · 0.85
runTestRunFunction · 0.85
renderCreateTextFunction · 0.85

Tested by

no test coverage detected