719032b19f
Update Schema / Update configuration json schema (push) Has been cancelled
Memory Benchmark / Memory Test (Full Analysis) (push) Has been cancelled
CI Quality / Lint GitHub Actions (actionlint) (push) Has been cancelled
CI Quality / Lint GitHub Actions (zizmor) (push) Has been cancelled
CI Quality / Check typos (push) Has been cancelled
CI / Test coverage (push) Has been cancelled
CI / Test (22.x, windows-latest) (push) Has been cancelled
CI / Test (24.x, macos-latest) (push) Has been cancelled
CI / Test (24.x, ubuntu-latest) (push) Has been cancelled
CI / Test (24.x, windows-latest) (push) Has been cancelled
CI / Test (26.x, macos-latest) (push) Has been cancelled
CI / Test (26.x, ubuntu-latest) (push) Has been cancelled
CI / Test (26.x, windows-latest) (push) Has been cancelled
CI / Test with Bun (latest, macos-latest) (push) Has been cancelled
CI / Test with Bun (latest, ubuntu-latest) (push) Has been cancelled
CI / Test with Bun (latest, windows-latest) (push) Has been cancelled
CI / Build and run (22.x, macos-latest) (push) Has been cancelled
CI / Build and run (22.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (22.x, windows-latest) (push) Has been cancelled
autofix.ci / autofix (push) Has been cancelled
CI Browser Extension / Lint Browser Extension (push) Has been cancelled
CI Browser Extension / Test Browser Extension (push) Has been cancelled
Memory Benchmark / Memory Test (push) Has been cancelled
CI Website / Lint Website Client (push) Has been cancelled
CI Website / Lint Website Server (push) Has been cancelled
CI Website / Test Website Server (push) Has been cancelled
CI Website / Bundle Website Server (push) Has been cancelled
CI / Lint Biome (push) Has been cancelled
CI / Lint oxlint (push) Has been cancelled
CI / Lint TypeScript (push) Has been cancelled
CI / Lint Secretlint (push) Has been cancelled
CI / Test (22.x, macos-latest) (push) Has been cancelled
CI / Test (22.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (24.x, macos-latest) (push) Has been cancelled
CI / Build and run (24.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (24.x, windows-latest) (push) Has been cancelled
CI / Build and run (26.x, macos-latest) (push) Has been cancelled
CI / Build and run (26.x, ubuntu-latest) (push) Has been cancelled
CI / Build and run (26.x, windows-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, macos-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, ubuntu-latest) (push) Has been cancelled
CI / Build and run with Bun (latest, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Docker / build (linux/amd64, ubuntu-latest) (push) Has been cancelled
Docker / build (linux/arm/v7, ubuntu-24.04-arm) (push) Has been cancelled
Docker / build (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Docker / merge (push) Has been cancelled
Pack repository with Repomix / pack-repo (push) Has been cancelled
Performance Benchmark History / Benchmark (macos-latest) (push) Has been cancelled
Performance Benchmark History / Benchmark (ubuntu-latest) (push) Has been cancelled
Performance Benchmark History / Benchmark (windows-latest) (push) Has been cancelled
Performance Benchmark History / Store Results (push) Has been cancelled
Test Repomix Action / Test Node.js 22 (push) Has been cancelled
Test Repomix Action / Test Node.js 24 (push) Has been cancelled
Test Repomix Action / Test Node.js 26 (push) Has been cancelled
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import * as v from 'valibot';
|
|
import { describe, expect, test } from 'vitest';
|
|
import { AppError } from '../src/utils/errorHandler.js';
|
|
import { validateRequest } from '../src/utils/validation.js';
|
|
|
|
// Covers the three distinct paths through validateRequest:
|
|
// 1. successful parse → returns typed output
|
|
// 2. ValiError → wrapped as AppError(400) with the original issues preserved
|
|
// on `.cause` (the exact contract classifyRejectReason relies on)
|
|
// 3. anything else → re-thrown unchanged
|
|
//
|
|
// Tiny self-contained schemas keep the test focused — packRequestSchema's own
|
|
// behavior is covered indirectly through classifyRejectReason's drift tests.
|
|
describe('validateRequest', () => {
|
|
const schema = v.pipe(
|
|
v.strictObject({
|
|
name: v.pipe(v.string(), v.minLength(1, 'Name is required')),
|
|
count: v.optional(v.number()),
|
|
}),
|
|
v.check((data) => data.count === undefined || data.count >= 0, 'Count must be non-negative'),
|
|
);
|
|
|
|
test('returns parsed output on valid input', () => {
|
|
const result = validateRequest(schema, { name: 'pack', count: 3 });
|
|
expect(result).toEqual({ name: 'pack', count: 3 });
|
|
});
|
|
|
|
test('wraps ValiError as AppError(400) and joins issue messages', () => {
|
|
expect.assertions(4);
|
|
try {
|
|
validateRequest(schema, { name: '' });
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(AppError);
|
|
const appError = error as AppError;
|
|
expect(appError.statusCode).toBe(400);
|
|
expect(appError.message).toContain('Name is required');
|
|
expect(appError.message.startsWith('Invalid request: ')).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('preserves original ValiError on `.cause` so classifyRejectReason can read `.issues`', () => {
|
|
// Load-bearing contract: dropping `cause` here would silently break
|
|
// pack_completed.rejectReason labeling in production.
|
|
expect.assertions(2);
|
|
try {
|
|
validateRequest(schema, { name: '' });
|
|
} catch (error) {
|
|
const cause = (error as AppError).cause;
|
|
expect(cause).toBeInstanceOf(v.ValiError);
|
|
expect((cause as v.ValiError<typeof schema>).issues.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('top-level check issue (no path) renders message without a stray leading `": "`', () => {
|
|
// `count: -1` fails the top-level v.check — valibot emits an issue with no
|
|
// path. The rendered message must not start with `": "`.
|
|
expect.assertions(2);
|
|
try {
|
|
validateRequest(schema, { name: 'pack', count: -1 });
|
|
} catch (error) {
|
|
const message = (error as AppError).message;
|
|
expect(message).toContain('Count must be non-negative');
|
|
// Anchor on the specific defect shape: `Invalid request: : <rest>` with
|
|
// an empty path between the two colons. A plain `not.toContain(': : ')`
|
|
// also works today, but the anchored regex documents the exact failure
|
|
// mode we're guarding against.
|
|
expect(message).not.toMatch(/^Invalid request: : /);
|
|
}
|
|
});
|
|
|
|
test('re-throws non-ValiError unchanged', () => {
|
|
// v.parse itself never throws non-ValiError, but a refine()-like callback
|
|
// could raise. Simulate with a schema whose v.check callback throws.
|
|
const exploding = v.pipe(
|
|
v.string(),
|
|
v.check(() => {
|
|
throw new RangeError('unexpected boom');
|
|
}, 'never reached'),
|
|
);
|
|
|
|
expect(() => validateRequest(exploding, 'anything')).toThrowError(RangeError);
|
|
});
|
|
});
|