orchid-orm-test-factory 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.esm.js +185 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +190 -0
- package/dist/index.js.map +1 -0
- package/jest-setup.ts +23 -0
- package/package.json +69 -0
- package/rollup.config.js +3 -0
- package/src/factory.test.ts +304 -0
- package/src/factory.ts +316 -0
- package/src/index.ts +1 -0
- package/src/test-utils.ts +87 -0
- package/tsconfig.json +13 -0
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AnyZodObject } from 'zod';
|
|
2
|
+
import { Query, EmptyObject, CreateData } from 'pqb';
|
|
3
|
+
import { InstanceToZod } from 'orchid-orm-schema-to-zod';
|
|
4
|
+
|
|
5
|
+
declare type FactoryOptions = {
|
|
6
|
+
sequence?: number;
|
|
7
|
+
sequenceDistance?: number;
|
|
8
|
+
maxTextLength?: number;
|
|
9
|
+
};
|
|
10
|
+
declare type metaKey = typeof metaKey;
|
|
11
|
+
declare const metaKey: unique symbol;
|
|
12
|
+
declare type Result<T extends TestFactory, Data, Omitted = Omit<Data, keyof T[metaKey]['omit']>> = EmptyObject extends T[metaKey]['pick'] ? Omitted : Pick<Omitted, {
|
|
13
|
+
[K in keyof Omitted]: K extends keyof T[metaKey]['pick'] ? K : never;
|
|
14
|
+
}[keyof Omitted]>;
|
|
15
|
+
declare type BuildArg<T extends TestFactory> = {
|
|
16
|
+
[K in keyof T[metaKey]['type']]?: T[metaKey]['type'][K] | ((sequence: number) => T[metaKey]['type'][K]);
|
|
17
|
+
} & Record<string, unknown>;
|
|
18
|
+
declare type BuildResult<T extends TestFactory, Data extends BuildArg<T>> = Result<T, BuildArg<T> extends Data ? T[metaKey]['type'] : T[metaKey]['type'] & {
|
|
19
|
+
[K in keyof Data]: Data[K] extends () => void ? ReturnType<Data[K]> : Data[K];
|
|
20
|
+
}>;
|
|
21
|
+
declare type CreateArg<T extends TestFactory> = CreateData<Omit<T['model'], 'inputType'> & {
|
|
22
|
+
inputType: {
|
|
23
|
+
[K in keyof T['model']['type']]?: T['model']['type'][K] | ((sequence: number) => T['model']['type'][K]);
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
declare type CreateResult<T extends TestFactory> = Result<T, T['model']['type']>;
|
|
27
|
+
declare class TestFactory<Q extends Query = Query, Schema extends AnyZodObject = AnyZodObject, Type extends EmptyObject = EmptyObject> {
|
|
28
|
+
model: Q;
|
|
29
|
+
schema: Schema;
|
|
30
|
+
private readonly data;
|
|
31
|
+
sequence: number;
|
|
32
|
+
private readonly omitValues;
|
|
33
|
+
private readonly pickValues;
|
|
34
|
+
[metaKey]: {
|
|
35
|
+
type: Type;
|
|
36
|
+
omit: EmptyObject;
|
|
37
|
+
pick: EmptyObject;
|
|
38
|
+
};
|
|
39
|
+
constructor(model: Q, schema: Schema, data?: Record<string, unknown>, options?: FactoryOptions);
|
|
40
|
+
set<T extends this, Meta extends {
|
|
41
|
+
type: EmptyObject;
|
|
42
|
+
}, Data extends {
|
|
43
|
+
[K in keyof Meta['type']]?: Meta['type'][K] | (() => Meta['type'][K]);
|
|
44
|
+
} & Record<string, unknown>>(this: T & {
|
|
45
|
+
[metaKey]: Meta;
|
|
46
|
+
}, data: Data): T & {
|
|
47
|
+
[metaKey]: Meta & {
|
|
48
|
+
type: Data;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
omit<T extends this, Keys extends {
|
|
52
|
+
[K in keyof T[metaKey]['type']]?: true;
|
|
53
|
+
}>(this: T, keys: Keys): T & {
|
|
54
|
+
[metaKey]: T[metaKey] & {
|
|
55
|
+
omit: Keys;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
pick<T extends this, Keys extends {
|
|
59
|
+
[K in keyof T[metaKey]['type']]?: true;
|
|
60
|
+
}>(this: T, keys: Keys): T & {
|
|
61
|
+
[metaKey]: T[metaKey] & {
|
|
62
|
+
pick: Keys;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
build<T extends this, Data extends BuildArg<T>>(this: T, data?: Data): BuildResult<T, Data>;
|
|
66
|
+
buildList<T extends this, Data extends BuildArg<T>>(this: T, qty: number, data?: Data): BuildResult<T, Data>[];
|
|
67
|
+
create<T extends this, Data extends CreateArg<T>>(this: T, data?: Data): Promise<CreateResult<T>>;
|
|
68
|
+
createList<T extends this, Data extends CreateArg<T>>(this: T, qty: number, data?: Data): Promise<CreateResult<T>[]>;
|
|
69
|
+
extend<T extends this>(this: T): new () => TestFactory<Q, Schema, Type>;
|
|
70
|
+
}
|
|
71
|
+
declare const createFactory: <T extends Query>(model: T, options?: FactoryOptions) => TestFactory<T, InstanceToZod<T>, T["type"]>;
|
|
72
|
+
|
|
73
|
+
export { TestFactory, createFactory };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { ZodNullable } from 'zod';
|
|
2
|
+
import { DateBaseColumn, IntegerBaseColumn, TextBaseColumn } from 'pqb';
|
|
3
|
+
import { instanceToZod } from 'orchid-orm-schema-to-zod';
|
|
4
|
+
import { generateMock } from '@anatine/zod-mock';
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
const omit = (obj, keys) => {
|
|
23
|
+
const res = __spreadValues({}, obj);
|
|
24
|
+
Object.keys(keys).forEach((key) => {
|
|
25
|
+
delete res[key];
|
|
26
|
+
});
|
|
27
|
+
return res;
|
|
28
|
+
};
|
|
29
|
+
const pick = (obj, keys) => {
|
|
30
|
+
const res = {};
|
|
31
|
+
Object.keys(keys).forEach((key) => {
|
|
32
|
+
const value = obj[key];
|
|
33
|
+
if (value !== void 0) {
|
|
34
|
+
res[key] = value;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return res;
|
|
38
|
+
};
|
|
39
|
+
const processCreateData = (factory, data, arg) => {
|
|
40
|
+
const pick2 = {};
|
|
41
|
+
for (const key in factory.model.shape) {
|
|
42
|
+
pick2[key] = true;
|
|
43
|
+
}
|
|
44
|
+
factory.model.primaryKeys.forEach((key) => {
|
|
45
|
+
if (factory.model.shape[key].dataType.includes("serial")) {
|
|
46
|
+
delete pick2[key];
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const result = {};
|
|
50
|
+
const fns = {};
|
|
51
|
+
const allData = arg ? __spreadValues(__spreadValues({}, data), arg) : data;
|
|
52
|
+
for (const key in allData) {
|
|
53
|
+
delete pick2[key];
|
|
54
|
+
const value = allData[key];
|
|
55
|
+
if (typeof value === "function") {
|
|
56
|
+
fns[key] = value;
|
|
57
|
+
} else {
|
|
58
|
+
result[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const pickedSchema = factory.schema.pick(pick2);
|
|
62
|
+
return () => {
|
|
63
|
+
Object.assign(result, generateMock(pickedSchema));
|
|
64
|
+
for (const key in fns) {
|
|
65
|
+
result[key] = fns[key](factory.sequence);
|
|
66
|
+
}
|
|
67
|
+
factory.sequence++;
|
|
68
|
+
return __spreadValues({}, result);
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const _TestFactory = class {
|
|
72
|
+
constructor(model, schema, data = {}, options = {}) {
|
|
73
|
+
this.model = model;
|
|
74
|
+
this.schema = schema;
|
|
75
|
+
this.data = data;
|
|
76
|
+
this.omitValues = {};
|
|
77
|
+
this.pickValues = {};
|
|
78
|
+
var _a;
|
|
79
|
+
if (options.sequence !== void 0) {
|
|
80
|
+
this.sequence = options.sequence;
|
|
81
|
+
} else {
|
|
82
|
+
let workerId = parseInt(process.env.JEST_WORKER_ID);
|
|
83
|
+
if (isNaN(workerId))
|
|
84
|
+
workerId = 1;
|
|
85
|
+
this.sequence = (workerId - 1) * ((_a = options.sequenceDistance) != null ? _a : 1e3) + 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
set(data) {
|
|
89
|
+
return Object.assign(Object.create(this), {
|
|
90
|
+
data: __spreadValues(__spreadValues({}, this.data), data)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
omit(keys) {
|
|
94
|
+
return Object.assign(Object.create(this), {
|
|
95
|
+
omitValues: __spreadValues(__spreadValues({}, this.omitValues), keys)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
pick(keys) {
|
|
99
|
+
return Object.assign(Object.create(this), {
|
|
100
|
+
pickValues: __spreadValues(__spreadValues({}, this.pickValues), keys)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
build(data) {
|
|
104
|
+
let schema = this.schema;
|
|
105
|
+
let arg = data ? __spreadValues(__spreadValues({}, this.data), data) : this.data;
|
|
106
|
+
if (this.omitValues) {
|
|
107
|
+
schema = schema.omit(this.omitValues);
|
|
108
|
+
arg = omit(arg, this.omitValues);
|
|
109
|
+
}
|
|
110
|
+
if (this.pickValues && Object.keys(this.pickValues).length) {
|
|
111
|
+
schema = schema.pick(this.pickValues);
|
|
112
|
+
arg = pick(arg, this.pickValues);
|
|
113
|
+
}
|
|
114
|
+
const result = generateMock(schema);
|
|
115
|
+
for (const key in arg) {
|
|
116
|
+
const value = arg[key];
|
|
117
|
+
if (typeof value === "function") {
|
|
118
|
+
result[key] = value(this.sequence);
|
|
119
|
+
} else {
|
|
120
|
+
result[key] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.sequence++;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
buildList(qty, data) {
|
|
127
|
+
return [...Array(qty)].map(() => this.build(data));
|
|
128
|
+
}
|
|
129
|
+
async create(data) {
|
|
130
|
+
const getData = processCreateData(this, this.data, data);
|
|
131
|
+
return await this.model.create(getData());
|
|
132
|
+
}
|
|
133
|
+
async createList(qty, data) {
|
|
134
|
+
const getData = processCreateData(this, this.data, data);
|
|
135
|
+
const arr = [...Array(qty)].map(getData);
|
|
136
|
+
return await this.model.createMany(arr);
|
|
137
|
+
}
|
|
138
|
+
extend() {
|
|
139
|
+
const { model, schema } = this;
|
|
140
|
+
return class extends _TestFactory {
|
|
141
|
+
constructor() {
|
|
142
|
+
super(model, schema);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
let TestFactory = _TestFactory;
|
|
148
|
+
const nowString = new Date().toISOString();
|
|
149
|
+
const createFactory = (model, options) => {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
const schema = instanceToZod(model);
|
|
152
|
+
const data = {};
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
for (const key in model.shape) {
|
|
155
|
+
const column = model.shape[key];
|
|
156
|
+
if (column instanceof DateBaseColumn) {
|
|
157
|
+
if (column.data.as instanceof IntegerBaseColumn) {
|
|
158
|
+
data[key] = (sequence) => now + sequence;
|
|
159
|
+
} else if (((_a = column.parseFn) == null ? void 0 : _a.call(column, nowString)) instanceof Date) {
|
|
160
|
+
data[key] = (sequence) => new Date(now + sequence);
|
|
161
|
+
} else {
|
|
162
|
+
data[key] = (sequence) => new Date(now + sequence).toISOString();
|
|
163
|
+
}
|
|
164
|
+
} else if (column instanceof TextBaseColumn) {
|
|
165
|
+
const max = (_b = options == null ? void 0 : options.maxTextLength) != null ? _b : 1e3;
|
|
166
|
+
const item = schema.shape[key];
|
|
167
|
+
const string = item instanceof ZodNullable ? item.unwrap() : item;
|
|
168
|
+
const maxCheck = string._def.checks.find(
|
|
169
|
+
(check) => check.kind === "max"
|
|
170
|
+
);
|
|
171
|
+
if (!maxCheck || maxCheck.value[0] > max) {
|
|
172
|
+
schema.shape[key] = item instanceof ZodNullable ? string.max(max).nullable() : string.max(max);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return new TestFactory(
|
|
177
|
+
model,
|
|
178
|
+
schema,
|
|
179
|
+
data,
|
|
180
|
+
options
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export { TestFactory, createFactory };
|
|
185
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/factory.ts"],"sourcesContent":["import { AnyZodObject, ZodNullable, ZodString, ZodTypeAny } from 'zod';\nimport {\n CreateData,\n DateBaseColumn,\n EmptyObject,\n IntegerBaseColumn,\n Query,\n TextBaseColumn,\n} from 'pqb';\nimport { instanceToZod, InstanceToZod } from 'orchid-orm-schema-to-zod';\nimport { generateMock } from '@anatine/zod-mock';\n\ntype FactoryOptions = {\n sequence?: number;\n sequenceDistance?: number;\n maxTextLength?: number;\n};\n\ntype metaKey = typeof metaKey;\nconst metaKey = Symbol('meta');\n\ntype Result<\n T extends TestFactory,\n Data,\n Omitted = Omit<Data, keyof T[metaKey]['omit']>,\n> = EmptyObject extends T[metaKey]['pick']\n ? Omitted\n : Pick<\n Omitted,\n {\n [K in keyof Omitted]: K extends keyof T[metaKey]['pick'] ? K : never;\n }[keyof Omitted]\n >;\n\ntype BuildArg<T extends TestFactory> = {\n [K in keyof T[metaKey]['type']]?:\n | T[metaKey]['type'][K]\n | ((sequence: number) => T[metaKey]['type'][K]);\n} & Record<string, unknown>;\n\ntype BuildResult<T extends TestFactory, Data extends BuildArg<T>> = Result<\n T,\n BuildArg<T> extends Data\n ? T[metaKey]['type']\n : T[metaKey]['type'] & {\n [K in keyof Data]: Data[K] extends () => void\n ? ReturnType<Data[K]>\n : Data[K];\n }\n>;\n\ntype CreateArg<T extends TestFactory> = CreateData<\n Omit<T['model'], 'inputType'> & {\n inputType: {\n [K in keyof T['model']['type']]?:\n | T['model']['type'][K]\n | ((sequence: number) => T['model']['type'][K]);\n };\n }\n>;\n\ntype CreateResult<T extends TestFactory> = Result<T, T['model']['type']>;\n\nconst omit = <T, Keys extends Record<string, unknown>>(\n obj: T,\n keys: Keys,\n): Omit<T, keyof Keys> => {\n const res = { ...obj };\n Object.keys(keys).forEach((key) => {\n delete (res as unknown as Record<string, unknown>)[key];\n });\n return res;\n};\n\nconst pick = <T, Keys extends Record<string, unknown>>(\n obj: T,\n keys: Keys,\n): Pick<T, { [K in keyof T]: K extends keyof Keys ? K : never }[keyof T]> => {\n const res = {} as T;\n Object.keys(keys).forEach((key) => {\n const value = (obj as unknown as Record<string, unknown>)[key];\n if (value !== undefined) {\n (res as unknown as Record<string, unknown>)[key] = value;\n }\n });\n return res;\n};\n\nconst processCreateData = <T extends TestFactory, Data extends CreateArg<T>>(\n factory: T,\n data: Record<string, unknown>,\n arg?: Data,\n) => {\n const pick: Record<string, true> = {};\n for (const key in factory.model.shape) {\n pick[key] = true;\n }\n\n factory.model.primaryKeys.forEach((key) => {\n if (factory.model.shape[key].dataType.includes('serial')) {\n delete pick[key];\n }\n });\n\n const result: Record<string, unknown> = {};\n\n const fns: Record<string, (sequence: number) => unknown> = {};\n\n const allData = (arg ? { ...data, ...arg } : data) as Record<string, unknown>;\n\n for (const key in allData) {\n delete pick[key];\n const value = allData[key];\n if (typeof value === 'function') {\n fns[key] = value as () => unknown;\n } else {\n result[key] = value;\n }\n }\n\n const pickedSchema = factory.schema.pick(pick);\n\n return () => {\n Object.assign(result, generateMock(pickedSchema));\n\n for (const key in fns) {\n result[key] = fns[key](factory.sequence);\n }\n\n factory.sequence++;\n\n return { ...result } as CreateData<T['model']>;\n };\n};\n\nexport class TestFactory<\n Q extends Query = Query,\n Schema extends AnyZodObject = AnyZodObject,\n Type extends EmptyObject = EmptyObject,\n> {\n sequence: number;\n private readonly omitValues: Record<PropertyKey, true> = {};\n private readonly pickValues: Record<PropertyKey, true> = {};\n\n [metaKey]!: {\n type: Type;\n omit: EmptyObject;\n pick: EmptyObject;\n };\n\n constructor(\n public model: Q,\n public schema: Schema,\n private readonly data: Record<string, unknown> = {},\n options: FactoryOptions = {},\n ) {\n if (options.sequence !== undefined) {\n this.sequence = options.sequence;\n } else {\n let workerId = parseInt(process.env.JEST_WORKER_ID as string);\n if (isNaN(workerId)) workerId = 1;\n this.sequence = (workerId - 1) * (options.sequenceDistance ?? 1000) + 1;\n }\n }\n\n set<\n T extends this,\n Meta extends { type: EmptyObject },\n Data extends {\n [K in keyof Meta['type']]?: Meta['type'][K] | (() => Meta['type'][K]);\n } & Record<string, unknown>,\n >(\n this: T & { [metaKey]: Meta },\n data: Data,\n ): T & { [metaKey]: Meta & { type: Data } } {\n return Object.assign(Object.create(this), {\n data: { ...this.data, ...data },\n });\n }\n\n omit<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(\n this: T,\n keys: Keys,\n ): T & { [metaKey]: T[metaKey] & { omit: Keys } } {\n return Object.assign(Object.create(this), {\n omitValues: { ...this.omitValues, ...keys },\n });\n }\n\n pick<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(\n this: T,\n keys: Keys,\n ): T & { [metaKey]: T[metaKey] & { pick: Keys } } {\n return Object.assign(Object.create(this), {\n pickValues: { ...this.pickValues, ...keys },\n });\n }\n\n build<T extends this, Data extends BuildArg<T>>(\n this: T,\n data?: Data,\n ): BuildResult<T, Data> {\n let schema = this.schema as AnyZodObject;\n let arg = data ? { ...this.data, ...data } : this.data;\n\n if (this.omitValues) {\n schema = schema.omit(this.omitValues);\n arg = omit(arg, this.omitValues);\n }\n\n if (this.pickValues && Object.keys(this.pickValues).length) {\n schema = schema.pick(this.pickValues);\n arg = pick(arg, this.pickValues);\n }\n\n const result = generateMock(schema) as Record<string, unknown>;\n for (const key in arg) {\n const value = (arg as Record<string, unknown>)[key];\n if (typeof value === 'function') {\n result[key] = value(this.sequence);\n } else {\n result[key] = value;\n }\n }\n\n this.sequence++;\n\n return result as BuildResult<T, Data>;\n }\n\n buildList<T extends this, Data extends BuildArg<T>>(\n this: T,\n qty: number,\n data?: Data,\n ): BuildResult<T, Data>[] {\n return [...Array(qty)].map(() => this.build(data));\n }\n\n async create<T extends this, Data extends CreateArg<T>>(\n this: T,\n data?: Data,\n ): Promise<CreateResult<T>> {\n const getData = processCreateData(this, this.data, data);\n return (await this.model.create(getData())) as CreateResult<T>;\n }\n\n async createList<T extends this, Data extends CreateArg<T>>(\n this: T,\n qty: number,\n data?: Data,\n ): Promise<CreateResult<T>[]> {\n const getData = processCreateData(this, this.data, data);\n const arr = [...Array(qty)].map(getData);\n return (await this.model.createMany(arr)) as CreateResult<T>[];\n }\n\n extend<T extends this>(this: T): new () => TestFactory<Q, Schema, Type> {\n const { model, schema } = this;\n\n return class extends TestFactory<Q, Schema, Type> {\n constructor() {\n super(model, schema);\n }\n };\n }\n}\n\nconst nowString = new Date().toISOString();\n\nexport const createFactory = <T extends Query>(\n model: T,\n options?: FactoryOptions,\n) => {\n const schema = instanceToZod(model);\n\n const data: Record<string, unknown> = {};\n const now = Date.now();\n\n for (const key in model.shape) {\n const column = model.shape[key];\n if (column instanceof DateBaseColumn) {\n if (column.data.as instanceof IntegerBaseColumn) {\n data[key] = (sequence: number) => now + sequence;\n } else if (column.parseFn?.(nowString) instanceof Date) {\n data[key] = (sequence: number) => new Date(now + sequence);\n } else {\n data[key] = (sequence: number) =>\n new Date(now + sequence).toISOString();\n }\n } else if (column instanceof TextBaseColumn) {\n const max = options?.maxTextLength ?? 1000;\n const item = schema.shape[key];\n const string = (\n item instanceof ZodNullable ? item.unwrap() : item\n ) as ZodString;\n\n const maxCheck = string._def.checks.find(\n (check) => check.kind === 'max',\n ) as { value: [number] } | undefined;\n\n if (!maxCheck || maxCheck.value[0] > max) {\n (schema.shape as Record<string, ZodTypeAny>)[key] =\n item instanceof ZodNullable\n ? string.max(max).nullable()\n : string.max(max);\n }\n }\n }\n\n return new TestFactory<T, InstanceToZod<T>, T['type']>(\n model,\n schema,\n data,\n options,\n );\n};\n"],"names":["pick"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var zod = require('zod');
|
|
6
|
+
var pqb = require('pqb');
|
|
7
|
+
var orchidOrmSchemaToZod = require('orchid-orm-schema-to-zod');
|
|
8
|
+
var zodMock = require('@anatine/zod-mock');
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
+
var __spreadValues = (a, b) => {
|
|
16
|
+
for (var prop in b || (b = {}))
|
|
17
|
+
if (__hasOwnProp.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
if (__getOwnPropSymbols)
|
|
20
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
21
|
+
if (__propIsEnum.call(b, prop))
|
|
22
|
+
__defNormalProp(a, prop, b[prop]);
|
|
23
|
+
}
|
|
24
|
+
return a;
|
|
25
|
+
};
|
|
26
|
+
const omit = (obj, keys) => {
|
|
27
|
+
const res = __spreadValues({}, obj);
|
|
28
|
+
Object.keys(keys).forEach((key) => {
|
|
29
|
+
delete res[key];
|
|
30
|
+
});
|
|
31
|
+
return res;
|
|
32
|
+
};
|
|
33
|
+
const pick = (obj, keys) => {
|
|
34
|
+
const res = {};
|
|
35
|
+
Object.keys(keys).forEach((key) => {
|
|
36
|
+
const value = obj[key];
|
|
37
|
+
if (value !== void 0) {
|
|
38
|
+
res[key] = value;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return res;
|
|
42
|
+
};
|
|
43
|
+
const processCreateData = (factory, data, arg) => {
|
|
44
|
+
const pick2 = {};
|
|
45
|
+
for (const key in factory.model.shape) {
|
|
46
|
+
pick2[key] = true;
|
|
47
|
+
}
|
|
48
|
+
factory.model.primaryKeys.forEach((key) => {
|
|
49
|
+
if (factory.model.shape[key].dataType.includes("serial")) {
|
|
50
|
+
delete pick2[key];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
const result = {};
|
|
54
|
+
const fns = {};
|
|
55
|
+
const allData = arg ? __spreadValues(__spreadValues({}, data), arg) : data;
|
|
56
|
+
for (const key in allData) {
|
|
57
|
+
delete pick2[key];
|
|
58
|
+
const value = allData[key];
|
|
59
|
+
if (typeof value === "function") {
|
|
60
|
+
fns[key] = value;
|
|
61
|
+
} else {
|
|
62
|
+
result[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const pickedSchema = factory.schema.pick(pick2);
|
|
66
|
+
return () => {
|
|
67
|
+
Object.assign(result, zodMock.generateMock(pickedSchema));
|
|
68
|
+
for (const key in fns) {
|
|
69
|
+
result[key] = fns[key](factory.sequence);
|
|
70
|
+
}
|
|
71
|
+
factory.sequence++;
|
|
72
|
+
return __spreadValues({}, result);
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const _TestFactory = class {
|
|
76
|
+
constructor(model, schema, data = {}, options = {}) {
|
|
77
|
+
this.model = model;
|
|
78
|
+
this.schema = schema;
|
|
79
|
+
this.data = data;
|
|
80
|
+
this.omitValues = {};
|
|
81
|
+
this.pickValues = {};
|
|
82
|
+
var _a;
|
|
83
|
+
if (options.sequence !== void 0) {
|
|
84
|
+
this.sequence = options.sequence;
|
|
85
|
+
} else {
|
|
86
|
+
let workerId = parseInt(process.env.JEST_WORKER_ID);
|
|
87
|
+
if (isNaN(workerId))
|
|
88
|
+
workerId = 1;
|
|
89
|
+
this.sequence = (workerId - 1) * ((_a = options.sequenceDistance) != null ? _a : 1e3) + 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
set(data) {
|
|
93
|
+
return Object.assign(Object.create(this), {
|
|
94
|
+
data: __spreadValues(__spreadValues({}, this.data), data)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
omit(keys) {
|
|
98
|
+
return Object.assign(Object.create(this), {
|
|
99
|
+
omitValues: __spreadValues(__spreadValues({}, this.omitValues), keys)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
pick(keys) {
|
|
103
|
+
return Object.assign(Object.create(this), {
|
|
104
|
+
pickValues: __spreadValues(__spreadValues({}, this.pickValues), keys)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
build(data) {
|
|
108
|
+
let schema = this.schema;
|
|
109
|
+
let arg = data ? __spreadValues(__spreadValues({}, this.data), data) : this.data;
|
|
110
|
+
if (this.omitValues) {
|
|
111
|
+
schema = schema.omit(this.omitValues);
|
|
112
|
+
arg = omit(arg, this.omitValues);
|
|
113
|
+
}
|
|
114
|
+
if (this.pickValues && Object.keys(this.pickValues).length) {
|
|
115
|
+
schema = schema.pick(this.pickValues);
|
|
116
|
+
arg = pick(arg, this.pickValues);
|
|
117
|
+
}
|
|
118
|
+
const result = zodMock.generateMock(schema);
|
|
119
|
+
for (const key in arg) {
|
|
120
|
+
const value = arg[key];
|
|
121
|
+
if (typeof value === "function") {
|
|
122
|
+
result[key] = value(this.sequence);
|
|
123
|
+
} else {
|
|
124
|
+
result[key] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.sequence++;
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
buildList(qty, data) {
|
|
131
|
+
return [...Array(qty)].map(() => this.build(data));
|
|
132
|
+
}
|
|
133
|
+
async create(data) {
|
|
134
|
+
const getData = processCreateData(this, this.data, data);
|
|
135
|
+
return await this.model.create(getData());
|
|
136
|
+
}
|
|
137
|
+
async createList(qty, data) {
|
|
138
|
+
const getData = processCreateData(this, this.data, data);
|
|
139
|
+
const arr = [...Array(qty)].map(getData);
|
|
140
|
+
return await this.model.createMany(arr);
|
|
141
|
+
}
|
|
142
|
+
extend() {
|
|
143
|
+
const { model, schema } = this;
|
|
144
|
+
return class extends _TestFactory {
|
|
145
|
+
constructor() {
|
|
146
|
+
super(model, schema);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
let TestFactory = _TestFactory;
|
|
152
|
+
const nowString = new Date().toISOString();
|
|
153
|
+
const createFactory = (model, options) => {
|
|
154
|
+
var _a, _b;
|
|
155
|
+
const schema = orchidOrmSchemaToZod.instanceToZod(model);
|
|
156
|
+
const data = {};
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
for (const key in model.shape) {
|
|
159
|
+
const column = model.shape[key];
|
|
160
|
+
if (column instanceof pqb.DateBaseColumn) {
|
|
161
|
+
if (column.data.as instanceof pqb.IntegerBaseColumn) {
|
|
162
|
+
data[key] = (sequence) => now + sequence;
|
|
163
|
+
} else if (((_a = column.parseFn) == null ? void 0 : _a.call(column, nowString)) instanceof Date) {
|
|
164
|
+
data[key] = (sequence) => new Date(now + sequence);
|
|
165
|
+
} else {
|
|
166
|
+
data[key] = (sequence) => new Date(now + sequence).toISOString();
|
|
167
|
+
}
|
|
168
|
+
} else if (column instanceof pqb.TextBaseColumn) {
|
|
169
|
+
const max = (_b = options == null ? void 0 : options.maxTextLength) != null ? _b : 1e3;
|
|
170
|
+
const item = schema.shape[key];
|
|
171
|
+
const string = item instanceof zod.ZodNullable ? item.unwrap() : item;
|
|
172
|
+
const maxCheck = string._def.checks.find(
|
|
173
|
+
(check) => check.kind === "max"
|
|
174
|
+
);
|
|
175
|
+
if (!maxCheck || maxCheck.value[0] > max) {
|
|
176
|
+
schema.shape[key] = item instanceof zod.ZodNullable ? string.max(max).nullable() : string.max(max);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return new TestFactory(
|
|
181
|
+
model,
|
|
182
|
+
schema,
|
|
183
|
+
data,
|
|
184
|
+
options
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
exports.TestFactory = TestFactory;
|
|
189
|
+
exports.createFactory = createFactory;
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/factory.ts"],"sourcesContent":["import { AnyZodObject, ZodNullable, ZodString, ZodTypeAny } from 'zod';\nimport {\n CreateData,\n DateBaseColumn,\n EmptyObject,\n IntegerBaseColumn,\n Query,\n TextBaseColumn,\n} from 'pqb';\nimport { instanceToZod, InstanceToZod } from 'orchid-orm-schema-to-zod';\nimport { generateMock } from '@anatine/zod-mock';\n\ntype FactoryOptions = {\n sequence?: number;\n sequenceDistance?: number;\n maxTextLength?: number;\n};\n\ntype metaKey = typeof metaKey;\nconst metaKey = Symbol('meta');\n\ntype Result<\n T extends TestFactory,\n Data,\n Omitted = Omit<Data, keyof T[metaKey]['omit']>,\n> = EmptyObject extends T[metaKey]['pick']\n ? Omitted\n : Pick<\n Omitted,\n {\n [K in keyof Omitted]: K extends keyof T[metaKey]['pick'] ? K : never;\n }[keyof Omitted]\n >;\n\ntype BuildArg<T extends TestFactory> = {\n [K in keyof T[metaKey]['type']]?:\n | T[metaKey]['type'][K]\n | ((sequence: number) => T[metaKey]['type'][K]);\n} & Record<string, unknown>;\n\ntype BuildResult<T extends TestFactory, Data extends BuildArg<T>> = Result<\n T,\n BuildArg<T> extends Data\n ? T[metaKey]['type']\n : T[metaKey]['type'] & {\n [K in keyof Data]: Data[K] extends () => void\n ? ReturnType<Data[K]>\n : Data[K];\n }\n>;\n\ntype CreateArg<T extends TestFactory> = CreateData<\n Omit<T['model'], 'inputType'> & {\n inputType: {\n [K in keyof T['model']['type']]?:\n | T['model']['type'][K]\n | ((sequence: number) => T['model']['type'][K]);\n };\n }\n>;\n\ntype CreateResult<T extends TestFactory> = Result<T, T['model']['type']>;\n\nconst omit = <T, Keys extends Record<string, unknown>>(\n obj: T,\n keys: Keys,\n): Omit<T, keyof Keys> => {\n const res = { ...obj };\n Object.keys(keys).forEach((key) => {\n delete (res as unknown as Record<string, unknown>)[key];\n });\n return res;\n};\n\nconst pick = <T, Keys extends Record<string, unknown>>(\n obj: T,\n keys: Keys,\n): Pick<T, { [K in keyof T]: K extends keyof Keys ? K : never }[keyof T]> => {\n const res = {} as T;\n Object.keys(keys).forEach((key) => {\n const value = (obj as unknown as Record<string, unknown>)[key];\n if (value !== undefined) {\n (res as unknown as Record<string, unknown>)[key] = value;\n }\n });\n return res;\n};\n\nconst processCreateData = <T extends TestFactory, Data extends CreateArg<T>>(\n factory: T,\n data: Record<string, unknown>,\n arg?: Data,\n) => {\n const pick: Record<string, true> = {};\n for (const key in factory.model.shape) {\n pick[key] = true;\n }\n\n factory.model.primaryKeys.forEach((key) => {\n if (factory.model.shape[key].dataType.includes('serial')) {\n delete pick[key];\n }\n });\n\n const result: Record<string, unknown> = {};\n\n const fns: Record<string, (sequence: number) => unknown> = {};\n\n const allData = (arg ? { ...data, ...arg } : data) as Record<string, unknown>;\n\n for (const key in allData) {\n delete pick[key];\n const value = allData[key];\n if (typeof value === 'function') {\n fns[key] = value as () => unknown;\n } else {\n result[key] = value;\n }\n }\n\n const pickedSchema = factory.schema.pick(pick);\n\n return () => {\n Object.assign(result, generateMock(pickedSchema));\n\n for (const key in fns) {\n result[key] = fns[key](factory.sequence);\n }\n\n factory.sequence++;\n\n return { ...result } as CreateData<T['model']>;\n };\n};\n\nexport class TestFactory<\n Q extends Query = Query,\n Schema extends AnyZodObject = AnyZodObject,\n Type extends EmptyObject = EmptyObject,\n> {\n sequence: number;\n private readonly omitValues: Record<PropertyKey, true> = {};\n private readonly pickValues: Record<PropertyKey, true> = {};\n\n [metaKey]!: {\n type: Type;\n omit: EmptyObject;\n pick: EmptyObject;\n };\n\n constructor(\n public model: Q,\n public schema: Schema,\n private readonly data: Record<string, unknown> = {},\n options: FactoryOptions = {},\n ) {\n if (options.sequence !== undefined) {\n this.sequence = options.sequence;\n } else {\n let workerId = parseInt(process.env.JEST_WORKER_ID as string);\n if (isNaN(workerId)) workerId = 1;\n this.sequence = (workerId - 1) * (options.sequenceDistance ?? 1000) + 1;\n }\n }\n\n set<\n T extends this,\n Meta extends { type: EmptyObject },\n Data extends {\n [K in keyof Meta['type']]?: Meta['type'][K] | (() => Meta['type'][K]);\n } & Record<string, unknown>,\n >(\n this: T & { [metaKey]: Meta },\n data: Data,\n ): T & { [metaKey]: Meta & { type: Data } } {\n return Object.assign(Object.create(this), {\n data: { ...this.data, ...data },\n });\n }\n\n omit<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(\n this: T,\n keys: Keys,\n ): T & { [metaKey]: T[metaKey] & { omit: Keys } } {\n return Object.assign(Object.create(this), {\n omitValues: { ...this.omitValues, ...keys },\n });\n }\n\n pick<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(\n this: T,\n keys: Keys,\n ): T & { [metaKey]: T[metaKey] & { pick: Keys } } {\n return Object.assign(Object.create(this), {\n pickValues: { ...this.pickValues, ...keys },\n });\n }\n\n build<T extends this, Data extends BuildArg<T>>(\n this: T,\n data?: Data,\n ): BuildResult<T, Data> {\n let schema = this.schema as AnyZodObject;\n let arg = data ? { ...this.data, ...data } : this.data;\n\n if (this.omitValues) {\n schema = schema.omit(this.omitValues);\n arg = omit(arg, this.omitValues);\n }\n\n if (this.pickValues && Object.keys(this.pickValues).length) {\n schema = schema.pick(this.pickValues);\n arg = pick(arg, this.pickValues);\n }\n\n const result = generateMock(schema) as Record<string, unknown>;\n for (const key in arg) {\n const value = (arg as Record<string, unknown>)[key];\n if (typeof value === 'function') {\n result[key] = value(this.sequence);\n } else {\n result[key] = value;\n }\n }\n\n this.sequence++;\n\n return result as BuildResult<T, Data>;\n }\n\n buildList<T extends this, Data extends BuildArg<T>>(\n this: T,\n qty: number,\n data?: Data,\n ): BuildResult<T, Data>[] {\n return [...Array(qty)].map(() => this.build(data));\n }\n\n async create<T extends this, Data extends CreateArg<T>>(\n this: T,\n data?: Data,\n ): Promise<CreateResult<T>> {\n const getData = processCreateData(this, this.data, data);\n return (await this.model.create(getData())) as CreateResult<T>;\n }\n\n async createList<T extends this, Data extends CreateArg<T>>(\n this: T,\n qty: number,\n data?: Data,\n ): Promise<CreateResult<T>[]> {\n const getData = processCreateData(this, this.data, data);\n const arr = [...Array(qty)].map(getData);\n return (await this.model.createMany(arr)) as CreateResult<T>[];\n }\n\n extend<T extends this>(this: T): new () => TestFactory<Q, Schema, Type> {\n const { model, schema } = this;\n\n return class extends TestFactory<Q, Schema, Type> {\n constructor() {\n super(model, schema);\n }\n };\n }\n}\n\nconst nowString = new Date().toISOString();\n\nexport const createFactory = <T extends Query>(\n model: T,\n options?: FactoryOptions,\n) => {\n const schema = instanceToZod(model);\n\n const data: Record<string, unknown> = {};\n const now = Date.now();\n\n for (const key in model.shape) {\n const column = model.shape[key];\n if (column instanceof DateBaseColumn) {\n if (column.data.as instanceof IntegerBaseColumn) {\n data[key] = (sequence: number) => now + sequence;\n } else if (column.parseFn?.(nowString) instanceof Date) {\n data[key] = (sequence: number) => new Date(now + sequence);\n } else {\n data[key] = (sequence: number) =>\n new Date(now + sequence).toISOString();\n }\n } else if (column instanceof TextBaseColumn) {\n const max = options?.maxTextLength ?? 1000;\n const item = schema.shape[key];\n const string = (\n item instanceof ZodNullable ? item.unwrap() : item\n ) as ZodString;\n\n const maxCheck = string._def.checks.find(\n (check) => check.kind === 'max',\n ) as { value: [number] } | undefined;\n\n if (!maxCheck || maxCheck.value[0] > max) {\n (schema.shape as Record<string, ZodTypeAny>)[key] =\n item instanceof ZodNullable\n ? string.max(max).nullable()\n : string.max(max);\n }\n }\n }\n\n return new TestFactory<T, InstanceToZod<T>, T['type']>(\n model,\n schema,\n data,\n options,\n );\n};\n"],"names":["pick","generateMock","instanceToZod","DateBaseColumn","IntegerBaseColumn","TextBaseColumn","ZodNullable"],"mappings":"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"}
|
package/jest-setup.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
patchPgForTransactions,
|
|
3
|
+
startTransaction,
|
|
4
|
+
rollbackTransaction,
|
|
5
|
+
} from 'pg-transactional-tests';
|
|
6
|
+
import { db } from './src/test-utils';
|
|
7
|
+
|
|
8
|
+
jest.mock('pqb', () => require('../pqb/src'), {
|
|
9
|
+
virtual: true,
|
|
10
|
+
});
|
|
11
|
+
jest.mock('orchid-orm', () => require('../orchid-orm/src'), {
|
|
12
|
+
virtual: true,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
patchPgForTransactions();
|
|
16
|
+
|
|
17
|
+
beforeAll(startTransaction);
|
|
18
|
+
beforeEach(startTransaction);
|
|
19
|
+
afterEach(rollbackTransaction);
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
await rollbackTransaction();
|
|
22
|
+
await db.$close();
|
|
23
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orchid-orm-test-factory",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Orchid ORM factories for tests",
|
|
5
|
+
"homepage": "https://orchid-orm.netlify.app/guide/orm-test-factories",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/romeerez/orchid-orm/tree/main/packages/test-factory"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"module": "dist/index.esm.js",
|
|
12
|
+
"typings": "dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"require": "./dist/index.js",
|
|
16
|
+
"import": "./dist/index.esm.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"jest": {
|
|
21
|
+
"setupFiles": [
|
|
22
|
+
"dotenv/config"
|
|
23
|
+
],
|
|
24
|
+
"globalSetup": "../../jest-global-setup.ts",
|
|
25
|
+
"setupFilesAfterEnv": [
|
|
26
|
+
"../../jest-setup.ts",
|
|
27
|
+
"./jest-setup.ts"
|
|
28
|
+
],
|
|
29
|
+
"transform": {
|
|
30
|
+
"^.+\\.ts$": "@swc/jest"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"orchid-orm",
|
|
35
|
+
"test",
|
|
36
|
+
"factory"
|
|
37
|
+
],
|
|
38
|
+
"author": "Roman Kushyn",
|
|
39
|
+
"license": "ISC",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"orchid-orm": "0.0.1",
|
|
42
|
+
"pqb": "0.5.4",
|
|
43
|
+
"orchid-orm-schema-to-zod": "0.0.1",
|
|
44
|
+
"@faker-js/faker": "^7.6.0",
|
|
45
|
+
"@anatine/zod-mock": "^3.6.0",
|
|
46
|
+
"zod": "^3.19.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"rollup": "^2.79.0",
|
|
50
|
+
"rollup-plugin-dts": "^4.2.2",
|
|
51
|
+
"rollup-plugin-esbuild": "^4.10.1",
|
|
52
|
+
"@swc/core": "^1.2.210",
|
|
53
|
+
"@swc/jest": "^0.2.21",
|
|
54
|
+
"@types/jest": "^28.1.2",
|
|
55
|
+
"@types/node": "^18.0.1",
|
|
56
|
+
"jest": "^28.1.2",
|
|
57
|
+
"rimraf": "^3.0.2",
|
|
58
|
+
"tslib": "^2.4.0",
|
|
59
|
+
"typescript": "^4.7.4",
|
|
60
|
+
"dotenv": "^16.0.1",
|
|
61
|
+
"pg-transactional-tests": "^1.0.5",
|
|
62
|
+
"pg": "^8.8.0",
|
|
63
|
+
"@types/pg": "^8.6.5"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"test": "jest",
|
|
67
|
+
"build": "rimraf ./dist/ && rollup -c --rollup.config"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { createFactory } from './factory';
|
|
2
|
+
import { assertType, db, User, Model, adapter } from './test-utils';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { orchidORM } from 'orchid-orm';
|
|
5
|
+
|
|
6
|
+
describe('factory', () => {
|
|
7
|
+
describe('sequence and sequenceDistance', () => {
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
process.env.JEST_WORKER_ID = '5';
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterAll(() => {
|
|
13
|
+
process.env.JEST_WORKER_ID = '1';
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should depend on process.env.JEST_WORKER_ID when it is defined', () => {
|
|
17
|
+
const factory = createFactory(db.user);
|
|
18
|
+
expect(factory.sequence).toBe(4001);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should allow to override sequence', () => {
|
|
22
|
+
const factory = createFactory(db.user, {
|
|
23
|
+
sequence: 123,
|
|
24
|
+
});
|
|
25
|
+
expect(factory.sequence).toBe(123);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should allow to override sequence distance', () => {
|
|
29
|
+
const factory = createFactory(db.user, {
|
|
30
|
+
sequenceDistance: 100,
|
|
31
|
+
});
|
|
32
|
+
expect(factory.sequence).toBe(401);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const userFactory = createFactory(db.user);
|
|
37
|
+
|
|
38
|
+
describe('build', () => {
|
|
39
|
+
it('should build an object for the model', () => {
|
|
40
|
+
const data = userFactory.build();
|
|
41
|
+
|
|
42
|
+
assertType<typeof data, User>();
|
|
43
|
+
|
|
44
|
+
expect(() => userFactory.schema.parse(data)).not.toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should accept data with values to override result', () => {
|
|
48
|
+
const data = userFactory.build({
|
|
49
|
+
age: 18,
|
|
50
|
+
name: 'name',
|
|
51
|
+
extra: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
assertType<
|
|
55
|
+
typeof data,
|
|
56
|
+
User & { name: 'name'; age: 18; extra: boolean }
|
|
57
|
+
>();
|
|
58
|
+
|
|
59
|
+
expect(data).toMatchObject({ age: 18, name: 'name', extra: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should accept data with functions to override result', () => {
|
|
63
|
+
const data = userFactory.build({
|
|
64
|
+
age: () => 18,
|
|
65
|
+
name: () => 'name',
|
|
66
|
+
extra: () => true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assertType<typeof data, User & { age: number; extra: true }>();
|
|
70
|
+
|
|
71
|
+
expect(data).toMatchObject({ age: 18, name: 'name', extra: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should limit long strings with 1000 by default', () => {
|
|
75
|
+
const profileFactory = createFactory(db.profile);
|
|
76
|
+
const data = profileFactory.build();
|
|
77
|
+
|
|
78
|
+
expect(data.bio.length).toBeLessThanOrEqual(1000);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should respect max which is set on column', () => {
|
|
82
|
+
class ProfileModel extends Model {
|
|
83
|
+
table = 'profile';
|
|
84
|
+
columns = this.setColumns((t) => ({
|
|
85
|
+
bio: t.text().min(100).max(120),
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const db = orchidORM(
|
|
90
|
+
{
|
|
91
|
+
adapter,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
profile: ProfileModel,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const profileFactory = createFactory(db.profile);
|
|
99
|
+
const data = profileFactory.build();
|
|
100
|
+
|
|
101
|
+
expect(data.bio.length).toBeLessThanOrEqual(120);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should allow to override maxTextLength', () => {
|
|
105
|
+
const profileFactory = createFactory(db.profile, {
|
|
106
|
+
maxTextLength: 500,
|
|
107
|
+
});
|
|
108
|
+
const data = profileFactory.build();
|
|
109
|
+
|
|
110
|
+
expect(data.bio.length).toBeLessThanOrEqual(500);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('buildList', () => {
|
|
115
|
+
const original = userFactory.build;
|
|
116
|
+
const buildMock = jest.fn();
|
|
117
|
+
|
|
118
|
+
beforeAll(() => {
|
|
119
|
+
userFactory.build = buildMock;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterAll(() => {
|
|
123
|
+
userFactory.build = original;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should call build provided number of times, pass the argument, return array', () => {
|
|
127
|
+
const arg = { extra: true };
|
|
128
|
+
const arr = userFactory.buildList(3, arg);
|
|
129
|
+
|
|
130
|
+
assertType<typeof arr, (User & { extra: boolean })[]>();
|
|
131
|
+
|
|
132
|
+
expect(buildMock).toHaveBeenCalledTimes(3);
|
|
133
|
+
expect(buildMock).toHaveBeenCalledWith(arg);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('omit', () => {
|
|
138
|
+
it('should allow to build data with omitted fields', () => {
|
|
139
|
+
const data = userFactory.omit({ id: true, name: true }).build();
|
|
140
|
+
|
|
141
|
+
assertType<typeof data, Omit<User, 'id' | 'name'>>();
|
|
142
|
+
|
|
143
|
+
expect(() =>
|
|
144
|
+
userFactory.schema.strict().omit({ id: true, name: true }).parse(data),
|
|
145
|
+
).not.toThrow();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('pick', () => {
|
|
150
|
+
it('should allow to build data with picked fields', () => {
|
|
151
|
+
const data = userFactory.pick({ id: true, name: true }).build();
|
|
152
|
+
|
|
153
|
+
assertType<typeof data, Pick<User, 'id' | 'name'>>();
|
|
154
|
+
|
|
155
|
+
expect(() =>
|
|
156
|
+
userFactory.schema.strict().pick({ id: true, name: true }).parse(data),
|
|
157
|
+
).not.toThrow();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('create', () => {
|
|
162
|
+
it('should create record with generated data, except serial primary keys, datetime numbers should be the same in the record and to be around now', async () => {
|
|
163
|
+
const item = await userFactory.create();
|
|
164
|
+
const now = Date.now();
|
|
165
|
+
|
|
166
|
+
expect(item.createdAt).toBe(item.updatedAt);
|
|
167
|
+
|
|
168
|
+
expect(Math.floor(item.createdAt / 1000)).toEqual(Math.floor(now / 1000));
|
|
169
|
+
|
|
170
|
+
assertType<typeof item, User>();
|
|
171
|
+
|
|
172
|
+
expect(() => userFactory.schema.parse(item)).not.toThrow();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should create record with overridden data', async () => {
|
|
176
|
+
const item = await userFactory.create({ name: 'name' });
|
|
177
|
+
|
|
178
|
+
assertType<typeof item, User>();
|
|
179
|
+
|
|
180
|
+
expect(item.name).toBe('name');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should create record with nested create', async () => {
|
|
184
|
+
const user = await userFactory.create({
|
|
185
|
+
profile: {
|
|
186
|
+
create: {
|
|
187
|
+
bio: 'bio',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
assertType<typeof user, User>();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('createList', () => {
|
|
197
|
+
it('should create a list of records, datetime numbers should be the same in one record and increase for each next record', async () => {
|
|
198
|
+
const items = await userFactory.createList(2);
|
|
199
|
+
|
|
200
|
+
assertType<typeof items, User[]>();
|
|
201
|
+
|
|
202
|
+
expect(items[0].name).not.toBe(items[1].name);
|
|
203
|
+
|
|
204
|
+
expect(items[0].createdAt).toEqual(items[0].updatedAt);
|
|
205
|
+
expect(items[1].createdAt).toEqual(items[1].updatedAt);
|
|
206
|
+
|
|
207
|
+
expect(items[0].createdAt).toBeLessThan(items[1].createdAt);
|
|
208
|
+
|
|
209
|
+
expect(() => z.array(userFactory.schema).parse(items)).not.toThrow();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should create a list of records with overridden data', async () => {
|
|
213
|
+
const items = await userFactory.createList(2, { name: 'name' });
|
|
214
|
+
|
|
215
|
+
assertType<typeof items, User[]>();
|
|
216
|
+
|
|
217
|
+
expect(items.map((item) => item.name)).toEqual(['name', 'name']);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('set', () => {
|
|
222
|
+
it('should set data to override result and work with build', () => {
|
|
223
|
+
const data = userFactory
|
|
224
|
+
.set({
|
|
225
|
+
age: 18,
|
|
226
|
+
})
|
|
227
|
+
.build({
|
|
228
|
+
name: 'name',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
assertType<typeof data, User & { age: number; name: 'name' }>();
|
|
232
|
+
|
|
233
|
+
expect(data).toMatchObject({ age: 18, name: 'name' });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should set data to override result and work with buildList', () => {
|
|
237
|
+
const arr = userFactory
|
|
238
|
+
.set({
|
|
239
|
+
age: 18,
|
|
240
|
+
})
|
|
241
|
+
.buildList(2, {
|
|
242
|
+
name: 'name',
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
assertType<typeof arr, (User & { age: number; name: 'name' })[]>();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should set data to override result and work with create', async () => {
|
|
249
|
+
const item = await userFactory.set({ age: 18 }).create();
|
|
250
|
+
|
|
251
|
+
assertType<typeof item, User>();
|
|
252
|
+
|
|
253
|
+
expect(() => userFactory.schema.parse(item)).not.toThrow();
|
|
254
|
+
expect(item.age).toBe(18);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should set data to override result and work with createList', async () => {
|
|
258
|
+
const items = await userFactory.set({ age: 18 }).createList(2);
|
|
259
|
+
|
|
260
|
+
assertType<typeof items, User[]>();
|
|
261
|
+
|
|
262
|
+
expect(() => z.array(userFactory.schema).parse(items)).not.toThrow();
|
|
263
|
+
expect(items.map((item) => item.age)).toEqual([18, 18]);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('custom methods', () => {
|
|
268
|
+
class ExtendedFactory extends createFactory(db.user).extend() {
|
|
269
|
+
specificUser(age: number) {
|
|
270
|
+
return this.otherMethod().set({
|
|
271
|
+
age,
|
|
272
|
+
name: 'specific',
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
otherMethod() {
|
|
276
|
+
return this.set({ extra: true });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const extendedFactory = new ExtendedFactory();
|
|
281
|
+
|
|
282
|
+
it('should respect omitted fields and build a proper object', async () => {
|
|
283
|
+
const data = extendedFactory.omit({ id: true }).specificUser(42).build();
|
|
284
|
+
|
|
285
|
+
assertType<
|
|
286
|
+
typeof data,
|
|
287
|
+
Omit<User, 'id'> & { age: number; extra: boolean }
|
|
288
|
+
>();
|
|
289
|
+
|
|
290
|
+
expect(data).toMatchObject({ age: 42, name: 'specific', extra: true });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should respect picked fields and build a proper object', async () => {
|
|
294
|
+
const data = extendedFactory
|
|
295
|
+
.pick({ age: true, name: true })
|
|
296
|
+
.specificUser(42)
|
|
297
|
+
.build();
|
|
298
|
+
|
|
299
|
+
assertType<typeof data, Pick<User, 'name'> & { age: number }>();
|
|
300
|
+
|
|
301
|
+
expect(data).toEqual({ age: 42, name: 'specific' });
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
package/src/factory.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { AnyZodObject, ZodNullable, ZodString, ZodTypeAny } from 'zod';
|
|
2
|
+
import {
|
|
3
|
+
CreateData,
|
|
4
|
+
DateBaseColumn,
|
|
5
|
+
EmptyObject,
|
|
6
|
+
IntegerBaseColumn,
|
|
7
|
+
Query,
|
|
8
|
+
TextBaseColumn,
|
|
9
|
+
} from 'pqb';
|
|
10
|
+
import { instanceToZod, InstanceToZod } from 'orchid-orm-schema-to-zod';
|
|
11
|
+
import { generateMock } from '@anatine/zod-mock';
|
|
12
|
+
|
|
13
|
+
type FactoryOptions = {
|
|
14
|
+
sequence?: number;
|
|
15
|
+
sequenceDistance?: number;
|
|
16
|
+
maxTextLength?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type metaKey = typeof metaKey;
|
|
20
|
+
const metaKey = Symbol('meta');
|
|
21
|
+
|
|
22
|
+
type Result<
|
|
23
|
+
T extends TestFactory,
|
|
24
|
+
Data,
|
|
25
|
+
Omitted = Omit<Data, keyof T[metaKey]['omit']>,
|
|
26
|
+
> = EmptyObject extends T[metaKey]['pick']
|
|
27
|
+
? Omitted
|
|
28
|
+
: Pick<
|
|
29
|
+
Omitted,
|
|
30
|
+
{
|
|
31
|
+
[K in keyof Omitted]: K extends keyof T[metaKey]['pick'] ? K : never;
|
|
32
|
+
}[keyof Omitted]
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
type BuildArg<T extends TestFactory> = {
|
|
36
|
+
[K in keyof T[metaKey]['type']]?:
|
|
37
|
+
| T[metaKey]['type'][K]
|
|
38
|
+
| ((sequence: number) => T[metaKey]['type'][K]);
|
|
39
|
+
} & Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
type BuildResult<T extends TestFactory, Data extends BuildArg<T>> = Result<
|
|
42
|
+
T,
|
|
43
|
+
BuildArg<T> extends Data
|
|
44
|
+
? T[metaKey]['type']
|
|
45
|
+
: T[metaKey]['type'] & {
|
|
46
|
+
[K in keyof Data]: Data[K] extends () => void
|
|
47
|
+
? ReturnType<Data[K]>
|
|
48
|
+
: Data[K];
|
|
49
|
+
}
|
|
50
|
+
>;
|
|
51
|
+
|
|
52
|
+
type CreateArg<T extends TestFactory> = CreateData<
|
|
53
|
+
Omit<T['model'], 'inputType'> & {
|
|
54
|
+
inputType: {
|
|
55
|
+
[K in keyof T['model']['type']]?:
|
|
56
|
+
| T['model']['type'][K]
|
|
57
|
+
| ((sequence: number) => T['model']['type'][K]);
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
type CreateResult<T extends TestFactory> = Result<T, T['model']['type']>;
|
|
63
|
+
|
|
64
|
+
const omit = <T, Keys extends Record<string, unknown>>(
|
|
65
|
+
obj: T,
|
|
66
|
+
keys: Keys,
|
|
67
|
+
): Omit<T, keyof Keys> => {
|
|
68
|
+
const res = { ...obj };
|
|
69
|
+
Object.keys(keys).forEach((key) => {
|
|
70
|
+
delete (res as unknown as Record<string, unknown>)[key];
|
|
71
|
+
});
|
|
72
|
+
return res;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const pick = <T, Keys extends Record<string, unknown>>(
|
|
76
|
+
obj: T,
|
|
77
|
+
keys: Keys,
|
|
78
|
+
): Pick<T, { [K in keyof T]: K extends keyof Keys ? K : never }[keyof T]> => {
|
|
79
|
+
const res = {} as T;
|
|
80
|
+
Object.keys(keys).forEach((key) => {
|
|
81
|
+
const value = (obj as unknown as Record<string, unknown>)[key];
|
|
82
|
+
if (value !== undefined) {
|
|
83
|
+
(res as unknown as Record<string, unknown>)[key] = value;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return res;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const processCreateData = <T extends TestFactory, Data extends CreateArg<T>>(
|
|
90
|
+
factory: T,
|
|
91
|
+
data: Record<string, unknown>,
|
|
92
|
+
arg?: Data,
|
|
93
|
+
) => {
|
|
94
|
+
const pick: Record<string, true> = {};
|
|
95
|
+
for (const key in factory.model.shape) {
|
|
96
|
+
pick[key] = true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
factory.model.primaryKeys.forEach((key) => {
|
|
100
|
+
if (factory.model.shape[key].dataType.includes('serial')) {
|
|
101
|
+
delete pick[key];
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result: Record<string, unknown> = {};
|
|
106
|
+
|
|
107
|
+
const fns: Record<string, (sequence: number) => unknown> = {};
|
|
108
|
+
|
|
109
|
+
const allData = (arg ? { ...data, ...arg } : data) as Record<string, unknown>;
|
|
110
|
+
|
|
111
|
+
for (const key in allData) {
|
|
112
|
+
delete pick[key];
|
|
113
|
+
const value = allData[key];
|
|
114
|
+
if (typeof value === 'function') {
|
|
115
|
+
fns[key] = value as () => unknown;
|
|
116
|
+
} else {
|
|
117
|
+
result[key] = value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const pickedSchema = factory.schema.pick(pick);
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
Object.assign(result, generateMock(pickedSchema));
|
|
125
|
+
|
|
126
|
+
for (const key in fns) {
|
|
127
|
+
result[key] = fns[key](factory.sequence);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
factory.sequence++;
|
|
131
|
+
|
|
132
|
+
return { ...result } as CreateData<T['model']>;
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export class TestFactory<
|
|
137
|
+
Q extends Query = Query,
|
|
138
|
+
Schema extends AnyZodObject = AnyZodObject,
|
|
139
|
+
Type extends EmptyObject = EmptyObject,
|
|
140
|
+
> {
|
|
141
|
+
sequence: number;
|
|
142
|
+
private readonly omitValues: Record<PropertyKey, true> = {};
|
|
143
|
+
private readonly pickValues: Record<PropertyKey, true> = {};
|
|
144
|
+
|
|
145
|
+
[metaKey]!: {
|
|
146
|
+
type: Type;
|
|
147
|
+
omit: EmptyObject;
|
|
148
|
+
pick: EmptyObject;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
constructor(
|
|
152
|
+
public model: Q,
|
|
153
|
+
public schema: Schema,
|
|
154
|
+
private readonly data: Record<string, unknown> = {},
|
|
155
|
+
options: FactoryOptions = {},
|
|
156
|
+
) {
|
|
157
|
+
if (options.sequence !== undefined) {
|
|
158
|
+
this.sequence = options.sequence;
|
|
159
|
+
} else {
|
|
160
|
+
let workerId = parseInt(process.env.JEST_WORKER_ID as string);
|
|
161
|
+
if (isNaN(workerId)) workerId = 1;
|
|
162
|
+
this.sequence = (workerId - 1) * (options.sequenceDistance ?? 1000) + 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
set<
|
|
167
|
+
T extends this,
|
|
168
|
+
Meta extends { type: EmptyObject },
|
|
169
|
+
Data extends {
|
|
170
|
+
[K in keyof Meta['type']]?: Meta['type'][K] | (() => Meta['type'][K]);
|
|
171
|
+
} & Record<string, unknown>,
|
|
172
|
+
>(
|
|
173
|
+
this: T & { [metaKey]: Meta },
|
|
174
|
+
data: Data,
|
|
175
|
+
): T & { [metaKey]: Meta & { type: Data } } {
|
|
176
|
+
return Object.assign(Object.create(this), {
|
|
177
|
+
data: { ...this.data, ...data },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
omit<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(
|
|
182
|
+
this: T,
|
|
183
|
+
keys: Keys,
|
|
184
|
+
): T & { [metaKey]: T[metaKey] & { omit: Keys } } {
|
|
185
|
+
return Object.assign(Object.create(this), {
|
|
186
|
+
omitValues: { ...this.omitValues, ...keys },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
pick<T extends this, Keys extends { [K in keyof T[metaKey]['type']]?: true }>(
|
|
191
|
+
this: T,
|
|
192
|
+
keys: Keys,
|
|
193
|
+
): T & { [metaKey]: T[metaKey] & { pick: Keys } } {
|
|
194
|
+
return Object.assign(Object.create(this), {
|
|
195
|
+
pickValues: { ...this.pickValues, ...keys },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
build<T extends this, Data extends BuildArg<T>>(
|
|
200
|
+
this: T,
|
|
201
|
+
data?: Data,
|
|
202
|
+
): BuildResult<T, Data> {
|
|
203
|
+
let schema = this.schema as AnyZodObject;
|
|
204
|
+
let arg = data ? { ...this.data, ...data } : this.data;
|
|
205
|
+
|
|
206
|
+
if (this.omitValues) {
|
|
207
|
+
schema = schema.omit(this.omitValues);
|
|
208
|
+
arg = omit(arg, this.omitValues);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (this.pickValues && Object.keys(this.pickValues).length) {
|
|
212
|
+
schema = schema.pick(this.pickValues);
|
|
213
|
+
arg = pick(arg, this.pickValues);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const result = generateMock(schema) as Record<string, unknown>;
|
|
217
|
+
for (const key in arg) {
|
|
218
|
+
const value = (arg as Record<string, unknown>)[key];
|
|
219
|
+
if (typeof value === 'function') {
|
|
220
|
+
result[key] = value(this.sequence);
|
|
221
|
+
} else {
|
|
222
|
+
result[key] = value;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.sequence++;
|
|
227
|
+
|
|
228
|
+
return result as BuildResult<T, Data>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
buildList<T extends this, Data extends BuildArg<T>>(
|
|
232
|
+
this: T,
|
|
233
|
+
qty: number,
|
|
234
|
+
data?: Data,
|
|
235
|
+
): BuildResult<T, Data>[] {
|
|
236
|
+
return [...Array(qty)].map(() => this.build(data));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async create<T extends this, Data extends CreateArg<T>>(
|
|
240
|
+
this: T,
|
|
241
|
+
data?: Data,
|
|
242
|
+
): Promise<CreateResult<T>> {
|
|
243
|
+
const getData = processCreateData(this, this.data, data);
|
|
244
|
+
return (await this.model.create(getData())) as CreateResult<T>;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async createList<T extends this, Data extends CreateArg<T>>(
|
|
248
|
+
this: T,
|
|
249
|
+
qty: number,
|
|
250
|
+
data?: Data,
|
|
251
|
+
): Promise<CreateResult<T>[]> {
|
|
252
|
+
const getData = processCreateData(this, this.data, data);
|
|
253
|
+
const arr = [...Array(qty)].map(getData);
|
|
254
|
+
return (await this.model.createMany(arr)) as CreateResult<T>[];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
extend<T extends this>(this: T): new () => TestFactory<Q, Schema, Type> {
|
|
258
|
+
const { model, schema } = this;
|
|
259
|
+
|
|
260
|
+
return class extends TestFactory<Q, Schema, Type> {
|
|
261
|
+
constructor() {
|
|
262
|
+
super(model, schema);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const nowString = new Date().toISOString();
|
|
269
|
+
|
|
270
|
+
export const createFactory = <T extends Query>(
|
|
271
|
+
model: T,
|
|
272
|
+
options?: FactoryOptions,
|
|
273
|
+
) => {
|
|
274
|
+
const schema = instanceToZod(model);
|
|
275
|
+
|
|
276
|
+
const data: Record<string, unknown> = {};
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
|
|
279
|
+
for (const key in model.shape) {
|
|
280
|
+
const column = model.shape[key];
|
|
281
|
+
if (column instanceof DateBaseColumn) {
|
|
282
|
+
if (column.data.as instanceof IntegerBaseColumn) {
|
|
283
|
+
data[key] = (sequence: number) => now + sequence;
|
|
284
|
+
} else if (column.parseFn?.(nowString) instanceof Date) {
|
|
285
|
+
data[key] = (sequence: number) => new Date(now + sequence);
|
|
286
|
+
} else {
|
|
287
|
+
data[key] = (sequence: number) =>
|
|
288
|
+
new Date(now + sequence).toISOString();
|
|
289
|
+
}
|
|
290
|
+
} else if (column instanceof TextBaseColumn) {
|
|
291
|
+
const max = options?.maxTextLength ?? 1000;
|
|
292
|
+
const item = schema.shape[key];
|
|
293
|
+
const string = (
|
|
294
|
+
item instanceof ZodNullable ? item.unwrap() : item
|
|
295
|
+
) as ZodString;
|
|
296
|
+
|
|
297
|
+
const maxCheck = string._def.checks.find(
|
|
298
|
+
(check) => check.kind === 'max',
|
|
299
|
+
) as { value: [number] } | undefined;
|
|
300
|
+
|
|
301
|
+
if (!maxCheck || maxCheck.value[0] > max) {
|
|
302
|
+
(schema.shape as Record<string, ZodTypeAny>)[key] =
|
|
303
|
+
item instanceof ZodNullable
|
|
304
|
+
? string.max(max).nullable()
|
|
305
|
+
: string.max(max);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return new TestFactory<T, InstanceToZod<T>, T['type']>(
|
|
311
|
+
model,
|
|
312
|
+
schema,
|
|
313
|
+
data,
|
|
314
|
+
options,
|
|
315
|
+
);
|
|
316
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './factory';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createModel, orchidORM } from 'orchid-orm';
|
|
2
|
+
import { Adapter, columnTypes } from 'pqb';
|
|
3
|
+
|
|
4
|
+
type AssertEqual<T, Expected> = [T] extends [Expected]
|
|
5
|
+
? [Expected] extends [T]
|
|
6
|
+
? true
|
|
7
|
+
: false
|
|
8
|
+
: false;
|
|
9
|
+
|
|
10
|
+
export const assertType = <T, Expected>(
|
|
11
|
+
..._: AssertEqual<T, Expected> extends true ? [] : ['invalid type']
|
|
12
|
+
) => {
|
|
13
|
+
// noop
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Model = createModel({
|
|
17
|
+
columnTypes: {
|
|
18
|
+
...columnTypes,
|
|
19
|
+
timestamp: () => columnTypes.timestamp().asNumber(),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type User = UserModel['columns']['type'];
|
|
24
|
+
class UserModel extends Model {
|
|
25
|
+
table = 'user';
|
|
26
|
+
columns = this.setColumns((t) => ({
|
|
27
|
+
id: t.serial().primaryKey(),
|
|
28
|
+
name: t.text(),
|
|
29
|
+
password: t.text(),
|
|
30
|
+
picture: t.text().nullable(),
|
|
31
|
+
data: t
|
|
32
|
+
.json((j) =>
|
|
33
|
+
j.object({
|
|
34
|
+
name: j.string(),
|
|
35
|
+
tags: j.string().array(),
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
38
|
+
.nullable(),
|
|
39
|
+
age: t.integer().nullable(),
|
|
40
|
+
active: t.boolean().nullable(),
|
|
41
|
+
...t.timestamps(),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
relations = {
|
|
45
|
+
profile: this.hasOne(() => ProfileModel, {
|
|
46
|
+
required: true,
|
|
47
|
+
primaryKey: 'id',
|
|
48
|
+
foreignKey: 'userId',
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class ProfileModel extends Model {
|
|
54
|
+
table = 'profile';
|
|
55
|
+
columns = this.setColumns((t) => ({
|
|
56
|
+
id: t.serial().primaryKey(),
|
|
57
|
+
userId: t
|
|
58
|
+
.integer()
|
|
59
|
+
.nullable()
|
|
60
|
+
.foreignKey(() => UserModel, 'id'),
|
|
61
|
+
bio: t.text().min(100).max(100000),
|
|
62
|
+
...t.timestamps(),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
relations = {
|
|
66
|
+
user: this.belongsTo(() => UserModel, {
|
|
67
|
+
required: true,
|
|
68
|
+
primaryKey: 'id',
|
|
69
|
+
foreignKey: 'userId',
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const adapter = new Adapter({
|
|
75
|
+
connectionString: process.env.DATABASE_URL,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const db = orchidORM(
|
|
79
|
+
{
|
|
80
|
+
adapter,
|
|
81
|
+
log: false,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
user: UserModel,
|
|
85
|
+
profile: ProfileModel,
|
|
86
|
+
},
|
|
87
|
+
);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"include": ["./src", "./jest-setup.ts"],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"noEmit": false,
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"paths": {
|
|
9
|
+
"orchid-orm": ["../orchid-orm/src"],
|
|
10
|
+
"orchid-orm-schema-to-zod": ["../schema-to-zod/src"]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|