/** * Zod Schemas for Validation * * Runtime validation schemas for adapter contracts and test specifications. * These schemas provide detailed validation with helpful error messages. */ import { z } from "zod"; /** * JSON value schema * * Matches JsonValue type from @wavespec/types */ const JsonValueSchema: z.ZodType = z.lazy(() => z.union([ z.string(), z.number(), z.boolean(), z.null(), z.array(JsonValueSchema), z.record(JsonValueSchema), ]), ); /** * Assertion schemas */ const SuccessAssertionSchema = z.object({ type: z.literal("success"), }); const ErrorAssertionSchema = z.object({ type: z.literal("error"), messageContains: z.string().optional(), }); const ContainsTextAssertionSchema = z.object({ type: z.literal("contains_text"), text: z.string(), caseInsensitive: z.boolean().optional(), }); const NotContainsTextAssertionSchema = z.object({ type: z.literal("not_contains_text"), text: z.string(), caseInsensitive: z.boolean().optional(), }); const ResponseTimeAssertionSchema = z.object({ type: z.literal("response_time_ms"), max: z.number().positive(), }); const JsonPathAssertionSchema = z.object({ type: z.literal("json_path"), path: z.string(), expected: z .union([ JsonValueSchema, z.object({ equals: JsonValueSchema.optional(), contains: z.array(JsonValueSchema).optional(), matches: z.string().optional(), }), ]) .optional(), }); const RegexMatchAssertionSchema = z.object({ type: z.literal("regex_match"), pattern: z.string(), flags: z.string().optional(), }); const SnapshotAssertionSchema = z.object({ type: z.literal("snapshot"), file: z.string(), ignoreFields: z.array(z.string()).optional(), }); const JsonSchemaAssertionSchema = z.object({ type: z.literal("json_schema"), schema: z.string(), }); const CustomAssertionSchema = z.object({ type: z.literal("custom"), module: z.string(), function: z.string(), params: z.record(z.any()).optional(), }); const AssertionSchema = z.discriminatedUnion("type", [ SuccessAssertionSchema, ErrorAssertionSchema, ContainsTextAssertionSchema, NotContainsTextAssertionSchema, ResponseTimeAssertionSchema, JsonPathAssertionSchema, RegexMatchAssertionSchema, SnapshotAssertionSchema, JsonSchemaAssertionSchema, CustomAssertionSchema, ]); /** * Universal step schema */ const UniversalStepSchema = z.object({ operation: z.string(), params: z.record(z.any()), saveAs: z.string().optional(), }); /** * Test specification schema * * Validates the structure of a test spec including operation, params, and assertions. */ export const TestSpecSchema = z.object({ name: z.string().min(1, "Test name is required"), description: z.string().optional(), operation: z.string().min(1, "Operation is required"), params: z.record(z.any()), assertions: z.array(AssertionSchema).min(1, "At least one assertion is required"), timeout: z.number().positive().optional(), skip: z.boolean().optional(), only: z.boolean().optional(), saveAs: z.string().optional(), setup: z.array(UniversalStepSchema).optional(), teardown: z.array(UniversalStepSchema).optional(), tags: z.array(z.string()).optional(), priority: z.enum(["critical", "high", "medium", "low"]).optional(), category: z.string().optional(), }); /** * Execution config schema */ const ExecutionConfigSchema = z.object({ mode: z.enum(["record", "replay", "passthrough"]).optional(), timeout: z.number().positive().optional(), isolation: z.enum(["per_suite", "per_test", "none"]).optional(), stopOnFailure: z.boolean().optional(), filters: z.array(z.string()).optional(), includeTags: z.array(z.string()).optional(), excludeTags: z.array(z.string()).optional(), dryRun: z.boolean().optional(), }); /** * Recording config schema */ const RecordingConfigSchema = z.object({ cassette_dir: z.string().optional(), redaction: z .object({ headers: z.array(z.string()).optional(), json_paths: z.array(z.string()).optional(), }) .optional(), }); /** * Suite config schema */ export const SuiteConfigSchema = z.object({ name: z.string().min(1, "Suite name is required"), description: z.string().optional(), adapter: z.string().optional(), tests: z.array(z.union([z.string(), TestSpecSchema])), server: z.record(z.any()).optional(), execution: ExecutionConfigSchema.optional(), recording: RecordingConfigSchema.optional(), tags: z.array(z.string()).optional(), config: z.record(JsonValueSchema).optional(), beforeAll: z.array(UniversalStepSchema).optional(), afterAll: z.array(UniversalStepSchema).optional(), }); /** * Suite spec schema */ export const SuiteSpecSchema = z.object({ version: z.string(), suite: SuiteConfigSchema, }); /** * Type exports for schema types */ export type TestSpecSchemaType = z.infer; export type SuiteConfigSchemaType = z.infer; export type SuiteSpecSchemaType = z.infer;