190 lines
6.4 KiB
TypeScript
190 lines
6.4 KiB
TypeScript
/**
|
||
* Schema 边缘场景测试
|
||
*/
|
||
|
||
import { createSchema, validateRow, getPrimaryKey, checkFieldType, astColumnToColumnDef } from '../../src/table/schema';
|
||
|
||
describe('Schema 边缘场景', () => {
|
||
// ---- createSchema ----
|
||
|
||
describe('createSchema', () => {
|
||
it('空列表抛出错误', () => {
|
||
expect(() => createSchema('empty', {})).toThrow('at least one column');
|
||
});
|
||
|
||
it('多主键定义(取第一个标记的)', () => {
|
||
const schema = createSchema('multi', {
|
||
id1: { type: 'string', primaryKey: true },
|
||
id2: { type: 'string' },
|
||
});
|
||
expect(getPrimaryKey(schema)).toBe('id1');
|
||
});
|
||
|
||
it('大表定义', () => {
|
||
const cols: Record<string, any> = {};
|
||
for (let i = 0; i < 50; i++) {
|
||
cols[`col${i}`] = { type: 'string' };
|
||
}
|
||
cols.id = { type: 'string', primaryKey: true };
|
||
const schema = createSchema('big', cols);
|
||
expect(Object.keys(schema.columns)).toHaveLength(51);
|
||
});
|
||
});
|
||
|
||
// ---- getPrimaryKey ----
|
||
|
||
describe('getPrimaryKey', () => {
|
||
it('没有标记主键时返回第一列', () => {
|
||
const schema = createSchema('test', {
|
||
uuid: { type: 'string', primaryKey: true },
|
||
name: { type: 'string' },
|
||
});
|
||
// 重新构造不带主键标记的 schema
|
||
const noPkSchema = { name: 'test', columns: {
|
||
first: { type: 'string' as const },
|
||
second: { type: 'string' as const },
|
||
}};
|
||
expect(getPrimaryKey(noPkSchema)).toBe('first');
|
||
});
|
||
});
|
||
|
||
// ---- validateRow ----
|
||
|
||
describe('validateRow', () => {
|
||
const schema = createSchema('users', {
|
||
id: { type: 'string', primaryKey: true },
|
||
name: { type: 'string', required: true },
|
||
bio: { type: 'string', maxLength: 200 },
|
||
score: { type: 'number', min: 0, max: 100, default: 50 },
|
||
active: { type: 'boolean', default: true },
|
||
birthday: { type: 'date' },
|
||
tags: { type: 'json', default: [] },
|
||
});
|
||
|
||
it('跳过 undefined 字段(使用默认值)', () => {
|
||
const row = validateRow(schema, { id: '1', name: 'Alice' });
|
||
expect(row.id).toBe('1');
|
||
expect(row.name).toBe('Alice');
|
||
expect(row.score).toBe(50);
|
||
expect(row.active).toBe(true);
|
||
expect(row.tags).toEqual([]);
|
||
});
|
||
|
||
it('null 值被视为不满足必填', () => {
|
||
expect(() => validateRow(schema, { id: '1', name: null })).toThrow('required');
|
||
});
|
||
|
||
it('json 类型接受对象', () => {
|
||
const row = validateRow(schema, { id: '1', name: 'Alice', tags: { key: 'value' } });
|
||
expect(row.tags).toEqual({ key: 'value' });
|
||
});
|
||
|
||
it('json 类型接受数组', () => {
|
||
const row = validateRow(schema, { id: '1', name: 'Alice', tags: [1, 2, 3] });
|
||
expect(row.tags).toEqual([1, 2, 3]);
|
||
});
|
||
|
||
it('date 类型接受有效日期字符串', () => {
|
||
const row = validateRow(schema, { id: '1', name: 'Alice', birthday: '2024-01-15' });
|
||
expect(row.birthday).toBe('2024-01-15');
|
||
});
|
||
|
||
it('date 类型拒绝无效格式', () => {
|
||
expect(() => validateRow(schema, { id: '1', name: 'Alice', birthday: 'not-a-date' }))
|
||
.toThrow('expects valid date');
|
||
expect(() => validateRow(schema, { id: '1', name: 'Alice', birthday: 123 }))
|
||
.toThrow('expects valid date');
|
||
});
|
||
|
||
it('boolean 类型', () => {
|
||
const row = validateRow(schema, { id: '1', name: 'Alice', active: false });
|
||
expect(row.active).toBe(false);
|
||
});
|
||
|
||
it('boolean 拒绝非布尔值', () => {
|
||
expect(() => validateRow(schema, { id: '1', name: 'Alice', active: 'yes' }))
|
||
.toThrow('expects boolean');
|
||
});
|
||
|
||
it('number 拒绝字符串', () => {
|
||
expect(() => validateRow(schema, { id: '1', name: 'Alice', score: 'high' }))
|
||
.toThrow('expects number');
|
||
});
|
||
|
||
it('string 拒绝数字', () => {
|
||
expect(() => validateRow(schema, { id: 123, name: 'Alice' }))
|
||
.toThrow('expects string');
|
||
});
|
||
});
|
||
|
||
// ---- checkFieldType ----
|
||
|
||
describe('checkFieldType', () => {
|
||
it('string 类型通过', () => {
|
||
expect(() => checkFieldType('t', 'c', 'string', 'hello')).not.toThrow();
|
||
});
|
||
|
||
it('string 类型拒绝', () => {
|
||
expect(() => checkFieldType('t', 'c', 'string', 123)).toThrow('expects string');
|
||
});
|
||
|
||
it('number 类型通过', () => {
|
||
expect(() => checkFieldType('t', 'c', 'number', 42)).not.toThrow();
|
||
expect(() => checkFieldType('t', 'c', 'number', 3.14)).not.toThrow();
|
||
expect(() => checkFieldType('t', 'c', 'number', 0)).not.toThrow();
|
||
});
|
||
|
||
it('boolean 类型通过', () => {
|
||
expect(() => checkFieldType('t', 'c', 'boolean', true)).not.toThrow();
|
||
expect(() => checkFieldType('t', 'c', 'boolean', false)).not.toThrow();
|
||
});
|
||
|
||
it('json 拒绝非对象', () => {
|
||
expect(() => checkFieldType('t', 'c', 'json', 'not-object')).toThrow('expects object');
|
||
expect(() => checkFieldType('t', 'c', 'json', 123)).toThrow('expects object');
|
||
});
|
||
});
|
||
|
||
// ---- 约束校验(v0.2.3) ----
|
||
describe('约束校验', () => {
|
||
it('string 超出 maxLength 抛出错误', () => {
|
||
expect(() => checkFieldType('t', 'c', 'string', 'hello', { type: 'string', maxLength: 3 }))
|
||
.toThrow('exceeds max length');
|
||
});
|
||
it('string 未超 maxLength 不报错', () => {
|
||
expect(() => checkFieldType('t', 'c', 'string', 'hi', { type: 'string', maxLength: 5 }))
|
||
.not.toThrow();
|
||
});
|
||
it('number 低于 min 抛出错误', () => {
|
||
expect(() => checkFieldType('t', 'c', 'number', -5, { type: 'number', min: 0 }))
|
||
.toThrow('below minimum');
|
||
});
|
||
it('number 高于 max 抛出错误', () => {
|
||
expect(() => checkFieldType('t', 'c', 'number', 200, { type: 'number', max: 100 }))
|
||
.toThrow('above maximum');
|
||
});
|
||
it('number 在范围内不报错', () => {
|
||
expect(() => checkFieldType('t', 'c', 'number', 50, { type: 'number', min: 0, max: 100 }))
|
||
.not.toThrow();
|
||
});
|
||
});
|
||
|
||
// ---- astColumnToColumnDef ----
|
||
|
||
describe('astColumnToColumnDef', () => {
|
||
it('转换基础列', () => {
|
||
const result = astColumnToColumnDef({ name: 'id', type: 'string' });
|
||
expect(result.type).toBe('string');
|
||
});
|
||
|
||
it('转换带修饰符的列', () => {
|
||
const result = astColumnToColumnDef({
|
||
name: 'id', type: 'string', primaryKey: true, unique: true, required: true,
|
||
});
|
||
expect(result.primaryKey).toBe(true);
|
||
expect(result.unique).toBe(true);
|
||
expect(result.required).toBe(true);
|
||
});
|
||
});
|
||
});
|