- Add zod dep; new src/shared/validation/schemas.ts (BucketName, JsonUploadPayload, LoginBody, DeleteObjects, CompleteMultipart, clampMaxKeys, parseOrNull) + test/validation.test.ts - Dedup timingSafeCompare -> src/shared/utils/crypto.ts (auth middleware, authenticate use-case, s3/auth now import it) - Dedup AuthSession -> single type in dto/auth.ts - Dedup telegram file URL builder + filename sanitizer to shared modules (file-controller now imports the canonical ones) - Remove duplicate controllers/home.html (canonical: src/home.html) - Fix stale bootstrap mocks to real module paths; bootstrap now asserts public GET vs guarded POST separately - Fix health assertion to include version field - package.json: test -> test:unit alias; new test:quarantine for network/live tests; register previously unlisted test files
90 lines
3.3 KiB
TypeScript
90 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
import {
|
|
BucketNameSchema,
|
|
CompleteMultipartBodySchema,
|
|
clampMaxKeys,
|
|
DeleteObjectsBodySchema,
|
|
JsonUploadPayloadSchema,
|
|
LoginBodySchema,
|
|
parseOrNull,
|
|
} from '../src/shared/validation/schemas';
|
|
|
|
describe('BucketNameSchema (single canonical validator)', () => {
|
|
it('accepts valid bucket names', () => {
|
|
expect(BucketNameSchema.safeParse('gitea').success).toBe(true);
|
|
expect(BucketNameSchema.safeParse('my.bucket-01').success).toBe(true);
|
|
});
|
|
|
|
it('rejects invalid names (format, dots, IP, xn--)', () => {
|
|
expect(BucketNameSchema.safeParse('ab').success).toBe(false);
|
|
expect(BucketNameSchema.safeParse('UPPERCASE').success).toBe(false);
|
|
expect(BucketNameSchema.safeParse('a..b').success).toBe(false);
|
|
expect(BucketNameSchema.safeParse('192.168.1.1').success).toBe(false);
|
|
expect(BucketNameSchema.safeParse('xn--bcher-kva').success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('JsonUploadPayloadSchema', () => {
|
|
it('accepts file + optional fileName', () => {
|
|
expect(
|
|
JsonUploadPayloadSchema.safeParse({ file: 'aGVsbG8=', fileName: 'hi.txt' }).success,
|
|
).toBe(true);
|
|
});
|
|
|
|
it('defaults fileName and rejects missing/empty file', () => {
|
|
const parsed = JsonUploadPayloadSchema.safeParse({ file: 'aGVsbG8=' });
|
|
expect(parsed.success && parsed.data.fileName).toBe('file');
|
|
expect(JsonUploadPayloadSchema.safeParse({}).success).toBe(false);
|
|
expect(JsonUploadPayloadSchema.safeParse({ file: '' }).success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('LoginBodySchema', () => {
|
|
it('accepts a non-empty token and rejects the rest', () => {
|
|
expect(LoginBodySchema.safeParse({ token: 'secret' }).success).toBe(true);
|
|
expect(LoginBodySchema.safeParse({}).success).toBe(false);
|
|
expect(LoginBodySchema.safeParse({ token: '' }).success).toBe(false);
|
|
expect(LoginBodySchema.safeParse({ token: 42 }).success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('DeleteObjectsBodySchema', () => {
|
|
it('accepts parsed keys and enforces the 1000-key S3 limit', () => {
|
|
expect(DeleteObjectsBodySchema.safeParse({ keys: ['a', 'b'], quiet: false }).success).toBe(
|
|
true,
|
|
);
|
|
expect(
|
|
DeleteObjectsBodySchema.safeParse({ keys: new Array(1001).fill('k'), quiet: true }).success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('CompleteMultipartBodySchema', () => {
|
|
it('accepts parsed parts and rejects bad part numbers', () => {
|
|
expect(
|
|
CompleteMultipartBodySchema.safeParse({ parts: [{ partNumber: 1, etag: 'abc' }] }).success,
|
|
).toBe(true);
|
|
expect(
|
|
CompleteMultipartBodySchema.safeParse({ parts: [{ partNumber: 0, etag: 'abc' }] }).success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('clampMaxKeys (unified listing clamp)', () => {
|
|
it('clamps into [1, 1000] and falls back to 1000 on garbage', () => {
|
|
expect(clampMaxKeys(null)).toBe(1000);
|
|
expect(clampMaxKeys('5')).toBe(5);
|
|
expect(clampMaxKeys('99999')).toBe(1000);
|
|
expect(clampMaxKeys('0')).toBe(1000);
|
|
expect(clampMaxKeys('-3')).toBe(1000);
|
|
expect(clampMaxKeys('abc')).toBe(1000);
|
|
});
|
|
});
|
|
|
|
describe('parseOrNull', () => {
|
|
it('returns the value on success and null on failure', () => {
|
|
expect(parseOrNull(LoginBodySchema, { token: 'x' })).toEqual({ token: 'x' });
|
|
expect(parseOrNull(LoginBodySchema, {})).toBeNull();
|
|
});
|
|
});
|