@cdwr/core
Version:
A set of core utilities for the Codeware ecosystem.
250 lines (243 loc) • 7.47 kB
JavaScript
// packages/core/src/lib/testing/create-schema-tests.ts
import { join as join2 } from "path";
import { describe, expect, it } from "vitest";
// packages/core/src/lib/testing/import-schema-files.ts
import fg from "fast-glob";
var importSchemaFiles = async (schemaDir) => {
const schemaFiles = await fg(["**/**.schema.ts"], {
cwd: schemaDir,
absolute: true
});
await Promise.all(schemaFiles.map((file) => import(file)));
};
// packages/core/src/lib/testing/run-schema-tests.ts
import { readFile } from "fs/promises";
import { join } from "path";
import fg2 from "fast-glob";
// packages/core/src/lib/testing/schema-registry.class.ts
var SchemaRegistry = class {
static registry = /* @__PURE__ */ new Map();
/**
* Registers a schema against a fixture key.
*
* The fixture key should represent where to find the fixture data.
* For example, you have a fixture named `fly-app.json` that contains
* api data matching the schema.
*
* The fixture is stored in `{fixtures}/api/fly-app.json`,
* hence the fixture key for this schema would be `api/fly-app`.
*
* @param fixtureKey - The fixture key to register the schema against
* @param schema - The schema to register
* @param metadata - The metadata for the schema
*
* @example
*
* ```ts
* // app.schema.ts
* // Create a schema that is used in your
* // application or library
* const AppSchema = z.object({
* name: z.string(),
* region: z.string()
* });
*
* // schemas.spec.ts
* // Register the schema against a fixture key
* // to enable automated schema validation.
* // Tag `api` to group related schemas in the test report.
* SchemaRegistry.register('api/fly-app', AppSchema, {
* name: 'Fly app',
* tags: ['api']
* });
* ```
*/
static register(fixtureKey, schema, metadata) {
this.registry.set(fixtureKey, { schema, ...metadata });
}
static get(fixtureKey) {
return this.registry.get(fixtureKey);
}
static getByTag(tag) {
return Array.from(this.registry.entries()).filter(
(entry) => entry[1].tags?.includes(tag)
);
}
static getAll() {
return Array.from(this.registry.entries());
}
};
// packages/core/src/lib/testing/validate-schema.ts
import { z } from "zod";
function validateSchema(schema, data, options = {}) {
const schemaName = options.name || "Schema";
try {
const parsed = schema.parse(data);
return {
success: true,
details: `${schemaName} validation passed`,
parsed
};
} catch (error) {
if (error instanceof z.ZodError) {
return {
success: false,
details: `${schemaName} validation failed`,
errors: error.errors.map((err) => ({
path: err.path,
code: err.code,
message: err.message
}))
};
}
return {
success: false,
details: `Unexpected error: ${error}`
};
}
}
// packages/core/src/lib/testing/run-schema-tests.ts
async function runSchemaTests(fixtureDir) {
const fixtures = await fg2([`**/*.json`], {
cwd: fixtureDir,
absolute: true
});
const results = [];
for (const fullPixturePath of fixtures) {
try {
const fixtureFile = fullPixturePath.replace(join(fixtureDir, "/"), "");
const fixtureKey = fixtureFile.replace(".json", "");
const metadata = SchemaRegistry.get(fixtureKey);
if (!metadata) {
results.push({
schemaName: `Unknown Schema (${fixtureKey})`,
status: "failure",
details: `No schema registered for fixture: ${fixtureKey}`,
fixtureFile,
fixtureKey,
metadata: void 0,
errors: null
});
continue;
}
const fixtureData = JSON.parse(await readFile(fullPixturePath, "utf-8"));
const validationResult = validateSchema(metadata.schema, fixtureData, {
name: metadata.name
});
results.push({
schemaName: metadata.name,
status: validationResult.success ? "success" : "failure",
details: validationResult.details,
fixtureFile,
fixtureKey,
transformed: validationResult.parsed,
metadata,
errors: validationResult.errors
});
} catch (error) {
results.push({
schemaName: fullPixturePath,
status: "failure",
details: `Failed to process fixture: ${error}`,
fixtureFile: "",
fixtureKey: "",
metadata: void 0,
errors: null
});
}
}
for (const [fixtureKey, metadata] of SchemaRegistry.getAll()) {
if (!results.some((result) => result.schemaName === metadata.name)) {
results.push({
schemaName: metadata.name,
status: "skipped",
details: `No fixture found for schema: ${metadata.name}`,
fixtureFile: "",
fixtureKey,
metadata,
errors: null
});
}
}
return results.sort((a, b) => a.schemaName.localeCompare(b.schemaName));
}
// packages/core/src/lib/testing/create-schema-tests.ts
var logTestReport = (results) => {
console.table(
results.map(({ schemaName, status, fixtureFile }) => ({
Schema: schemaName,
Fixture: fixtureFile,
Status: status === "success" ? "\u2705" : status === "failure" ? "\u274C" : "\u26A0\uFE0F"
}))
);
};
var createSchemaTests = (options) => {
const {
schemaDir,
fixturesPath = "__fixtures__",
description = "Schema Validation",
groups = [],
snapshots = false
} = options;
describe(description, () => {
it("validates all schemas against fixtures", async () => {
await importSchemaFiles(schemaDir);
const results = await runSchemaTests(join2(schemaDir, fixturesPath));
if (groups.length > 0) {
groups.forEach(({ name, tags }) => {
const groupResults = results.filter(
(result) => tags.some((tag) => result.metadata?.tags?.includes(tag))
);
if (groupResults.length > 0) {
console.log(`
${name} Schemas:`);
logTestReport(groupResults);
}
});
const ungroupedResults = results.filter(
(result) => !groups.some(
({ tags }) => tags.some((tag) => result.metadata?.tags?.includes(tag))
)
);
if (ungroupedResults.length > 0) {
console.log("\nOther Schemas:");
logTestReport(ungroupedResults);
}
} else if (results.length > 0) {
logTestReport(results);
} else {
console.log("No schemas to validate");
}
results.forEach(({ schemaName, details, errors, status }, index) => {
if (status === "success") {
return;
}
console.log(
`
${status === "skipped" ? "\u26A0\uFE0F " : "\u{1F534} "} [${index}] Schema validation ${status}: ${schemaName}`
);
if (details) {
console.log(" Details:", details);
}
if (errors) {
console.log(" Errors:", errors);
}
});
results.forEach((result) => {
if (result.status === "success" && snapshots) {
expect(result.transformed).toMatchSnapshot(
`${result.schemaName} transformed shape`
);
}
expect(
result.status !== "failure",
`Schema ${result.schemaName} validation failed`
).toBe(true);
});
});
});
};
export {
SchemaRegistry,
createSchemaTests
};