prisma-mock 1.1.0-alpha.1 → 1.1.0-alpha.3
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 +36 -8
- package/lib/client.d.ts +1 -0
- package/lib/client.js +7 -2
- package/lib/defaults/index.js +2 -0
- package/lib/defaults/objectId.d.ts +6 -0
- package/lib/defaults/objectId.js +19 -0
- package/lib/delegate.js +6 -4
- package/lib/index.d.ts +1 -0
- package/lib/legacy.d.ts +69 -0
- package/lib/utils/queryMatching.js +38 -6
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -14,9 +14,30 @@ yarn add prisma-mock --dev
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
-
###
|
|
17
|
+
### Prisma 7
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
With Prisma 7, you typically generate your client to a custom output directory (such as `./generated/client`), and import the generated `Prisma` namespace from your generated files. Use the `prisma-mock/client` entry point and pass the `Prisma` namespace when
|
|
20
|
+
initializing your mock client.
|
|
21
|
+
|
|
22
|
+
> **Note:** You will also need to configure and use the [DMMF Generator](#dmmf-generator) to produce the necessary metadata for mocking.
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import createPrismaMock from "prisma-mock/client"
|
|
26
|
+
import { Prisma } from "./generated/client"
|
|
27
|
+
import * as dmmf from "./generated/dmmf"
|
|
28
|
+
|
|
29
|
+
let client
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
client = createPrismaMock(Prisma, {
|
|
33
|
+
datamodel: dmmf,
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Prisma 6 and below
|
|
39
|
+
|
|
40
|
+
Simple example of how to create a prisma mock instance using the default (node_module) export:
|
|
20
41
|
|
|
21
42
|
```js
|
|
22
43
|
import createPrismaMock from "prisma-mock"
|
|
@@ -97,7 +118,11 @@ createPrismaMock<P extends PrismaClient = PrismaClient>(
|
|
|
97
118
|
caseInsensitive?: boolean
|
|
98
119
|
enableIndexes?: boolean
|
|
99
120
|
}
|
|
100
|
-
): P & {
|
|
121
|
+
): P & {
|
|
122
|
+
$getInternalState: () => Required<PrismaMockData<P>>
|
|
123
|
+
$setInternalState: (state: Required<PrismaMockData<P>>) => void
|
|
124
|
+
$clear: () => void
|
|
125
|
+
}
|
|
101
126
|
```
|
|
102
127
|
|
|
103
128
|
### Parameters
|
|
@@ -117,6 +142,8 @@ createPrismaMock<P extends PrismaClient = PrismaClient>(
|
|
|
117
142
|
Returns a mock Prisma client with all standard model methods plus:
|
|
118
143
|
|
|
119
144
|
- `$getInternalState()`: Method to access the internal data state for testing/debugging
|
|
145
|
+
- `$setInternalState(state)`: Method to replace the entire internal data state. Useful for resetting state to a specific snapshot or setting up complex test scenarios. The state is deep copied to avoid reference issues.
|
|
146
|
+
- `$clear()`: Method to reset the internal state back to the initial state
|
|
120
147
|
|
|
121
148
|
### Client Export (`prisma-mock/client`)
|
|
122
149
|
|
|
@@ -130,7 +157,11 @@ createPrismaMock<PClient extends PrismaClient, P extends typeof Prisma = typeof
|
|
|
130
157
|
caseInsensitive?: boolean
|
|
131
158
|
enableIndexes?: boolean
|
|
132
159
|
}
|
|
133
|
-
): PClient & {
|
|
160
|
+
): PClient & {
|
|
161
|
+
$getInternalState: () => Required<PrismaMockData<PClient>>
|
|
162
|
+
$setInternalState: (state: Required<PrismaMockData<PClient>>) => void
|
|
163
|
+
$clear: () => void
|
|
164
|
+
}
|
|
134
165
|
```
|
|
135
166
|
|
|
136
167
|
#### Parameters
|
|
@@ -284,6 +315,7 @@ const client = createPrismaMock(Prisma, {
|
|
|
284
315
|
### Attribute Functions ✅
|
|
285
316
|
|
|
286
317
|
- `autoincrement()`
|
|
318
|
+
- `auto()` (MongoDB ObjectId)
|
|
287
319
|
- `cuid()`
|
|
288
320
|
- `uuid()`
|
|
289
321
|
- `now()`
|
|
@@ -331,10 +363,6 @@ The following features are planned but not yet implemented:
|
|
|
331
363
|
- `isEmpty`
|
|
332
364
|
- `equals`
|
|
333
365
|
|
|
334
|
-
### Attributes
|
|
335
|
-
|
|
336
|
-
- `auto()`
|
|
337
|
-
|
|
338
366
|
### Referential Actions
|
|
339
367
|
|
|
340
368
|
- `onDelete: Restrict`
|
package/lib/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Prisma, PrismaClient } from "@prisma/client";
|
|
|
2
2
|
import { MockPrismaOptions, PrismaMockData } from "./types";
|
|
3
3
|
declare function createPrismaMock<PClient extends PrismaClient, P extends typeof Prisma = typeof Prisma>(prisma: P, options?: MockPrismaOptions<PClient, P>): PClient & {
|
|
4
4
|
$getInternalState: () => Required<PrismaMockData<PClient>>;
|
|
5
|
+
$setInternalState: (state: Required<PrismaMockData<PClient>>) => void;
|
|
5
6
|
$clear: () => void;
|
|
6
7
|
};
|
|
7
8
|
export default createPrismaMock;
|
package/lib/client.js
CHANGED
|
@@ -22,9 +22,10 @@ function createPrismaMock(prisma, options = {
|
|
|
22
22
|
enableIndexes: true,
|
|
23
23
|
data: {}
|
|
24
24
|
}) {
|
|
25
|
+
let internalState = options.data ? (0, deepCopy_1.deepCopy)(options.data) : {};
|
|
25
26
|
// Reference object to hold the mock data state
|
|
26
27
|
let ref = {
|
|
27
|
-
data:
|
|
28
|
+
data: internalState,
|
|
28
29
|
};
|
|
29
30
|
// Initialize the mock client (either use provided one or create new)
|
|
30
31
|
let client = options.mockClient ? options.mockClient : {};
|
|
@@ -126,8 +127,12 @@ function createPrismaMock(prisma, options = {
|
|
|
126
127
|
});
|
|
127
128
|
// Add method to access internal state for testing/debugging
|
|
128
129
|
client['$getInternalState'] = () => ref.data;
|
|
130
|
+
client['$setInternalState'] = (state) => {
|
|
131
|
+
internalState = (0, deepCopy_1.deepCopy)(state);
|
|
132
|
+
ref.data = internalState;
|
|
133
|
+
};
|
|
129
134
|
client['$clear'] = () => {
|
|
130
|
-
ref.data =
|
|
135
|
+
ref.data = internalState;
|
|
131
136
|
};
|
|
132
137
|
// @ts-ignore
|
|
133
138
|
return client;
|
package/lib/defaults/index.js
CHANGED
|
@@ -6,11 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const autoincrement_1 = __importDefault(require("./autoincrement"));
|
|
7
7
|
const cuid_1 = __importDefault(require("./cuid"));
|
|
8
8
|
const now_1 = __importDefault(require("./now"));
|
|
9
|
+
const objectId_1 = __importDefault(require("./objectId"));
|
|
9
10
|
const uuid_1 = __importDefault(require("./uuid"));
|
|
10
11
|
function createHandleDefault() {
|
|
11
12
|
// const registry = new Map<string, (string, Prisma.DMMF.Field, PrismaMockData) => any>();
|
|
12
13
|
const registry = new Map();
|
|
13
14
|
registry.set("autoincrement", (0, autoincrement_1.default)());
|
|
15
|
+
registry.set("auto", (0, objectId_1.default)());
|
|
14
16
|
registry.set("cuid", (0, cuid_1.default)());
|
|
15
17
|
registry.set("uuid", (0, uuid_1.default)());
|
|
16
18
|
registry.set("now", now_1.default);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Generates MongoDB ObjectId-like strings (24 hex chars).
|
|
5
|
+
* Used for @default(auto()) with @db.ObjectId
|
|
6
|
+
*/
|
|
7
|
+
const createObjectId = () => {
|
|
8
|
+
let counter = 0;
|
|
9
|
+
return () => {
|
|
10
|
+
const hex = "0123456789abcdef";
|
|
11
|
+
let result = "";
|
|
12
|
+
for (let i = 0; i < 22; i++) {
|
|
13
|
+
result += hex[Math.floor(Math.random() * 16)];
|
|
14
|
+
}
|
|
15
|
+
result += (++counter).toString(16).padStart(2, "0");
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
exports.default = createObjectId;
|
package/lib/delegate.js
CHANGED
|
@@ -133,7 +133,7 @@ const createDelegate = ({ ref, prisma, datamodel = prisma.dmmf.datamodel, caseIn
|
|
|
133
133
|
if (isCreating && (field.isUnique || field.isId)) {
|
|
134
134
|
const existing = findOne({ where: { [field.name]: inputFieldData } });
|
|
135
135
|
if (existing) {
|
|
136
|
-
(0, errors_1.throwKnownError)(prisma, `Unique constraint failed on the fields: (\`${field.name}\`)`, { code: 'P2002', meta: { target: [field.name] } });
|
|
136
|
+
(0, errors_1.throwKnownError)(prisma, `Unique constraint failed on the fields: (\`${field.name}\`)`, { code: 'P2002', meta: { modelName: model.name, target: [field.name] } });
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
// Handle relation fields (object type)
|
|
@@ -509,7 +509,9 @@ const createDelegate = ({ ref, prisma, datamodel = prisma.dmmf.datamodel, caseIn
|
|
|
509
509
|
const findOrThrow = (args) => {
|
|
510
510
|
const found = findOne(args);
|
|
511
511
|
if (!found) {
|
|
512
|
-
(0, errors_1.throwKnownError)(prisma, `No ${prop.slice(0, 1).toUpperCase()}${prop.slice(1)} found
|
|
512
|
+
(0, errors_1.throwKnownError)(prisma, `No ${prop.slice(0, 1).toUpperCase()}${prop.slice(1)} found`, {
|
|
513
|
+
meta: { cause: "No record was found for a query.", modelName: model.name },
|
|
514
|
+
});
|
|
513
515
|
}
|
|
514
516
|
return found;
|
|
515
517
|
};
|
|
@@ -824,7 +826,7 @@ const createDelegate = ({ ref, prisma, datamodel = prisma.dmmf.datamodel, caseIn
|
|
|
824
826
|
if (!hasMatch) {
|
|
825
827
|
if (args.skipForeignKeysChecks)
|
|
826
828
|
return;
|
|
827
|
-
(0, errors_1.throwKnownError)(prisma, "An operation failed because it depends on one or more records that were required but not found. Record to update not found.", { meta: { cause: "
|
|
829
|
+
(0, errors_1.throwKnownError)(prisma, "An operation failed because it depends on one or more records that were required but not found. Record to update not found.", { meta: { cause: "No record was found for an update.", modelName: model.name } });
|
|
828
830
|
}
|
|
829
831
|
ref.data = {
|
|
830
832
|
...ref.data,
|
|
@@ -1063,7 +1065,7 @@ const createDelegate = ({ ref, prisma, datamodel = prisma.dmmf.datamodel, caseIn
|
|
|
1063
1065
|
delete: (args) => {
|
|
1064
1066
|
const item = findOne(args);
|
|
1065
1067
|
if (!item) {
|
|
1066
|
-
(0, errors_1.throwKnownError)(prisma, "An operation failed because it depends on one or more records that were required but not found. Record to delete does not exist.", { meta: { cause: "
|
|
1068
|
+
(0, errors_1.throwKnownError)(prisma, "An operation failed because it depends on one or more records that were required but not found. Record to delete does not exist.", { meta: { cause: "No record was found for a delete.", modelName: model.name } });
|
|
1067
1069
|
}
|
|
1068
1070
|
const deleted = deleteMany(args);
|
|
1069
1071
|
if (deleted.length) {
|
package/lib/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ type MockPrismaOptions<P extends PrismaClient = PrismaClient> = Omit<MockPrismaO
|
|
|
5
5
|
};
|
|
6
6
|
export default function createPrismaClient<P extends PrismaClient = PrismaClient>(options?: MockPrismaOptions<P>): P & {
|
|
7
7
|
$getInternalState: () => Required<Partial<{ [key in import("./types").IsTable<Uncapitalize<import("./types").IsString<keyof P>>>]: import("./types").PrismaList<P, key>; }>>;
|
|
8
|
+
$setInternalState: (state: Required<Partial<{ [key in import("./types").IsTable<Uncapitalize<import("./types").IsString<keyof P>>>]: import("./types").PrismaList<P, key>; }>>) => void;
|
|
8
9
|
$clear: () => void;
|
|
9
10
|
};
|
|
10
11
|
export {};
|
package/lib/legacy.d.ts
CHANGED
|
@@ -73,5 +73,74 @@ export default function createPrismaClient(data?: PrismaMockData<PrismaClient>,
|
|
|
73
73
|
id: string;
|
|
74
74
|
}>[];
|
|
75
75
|
}>>;
|
|
76
|
+
$setInternalState: (state: Required<Partial<{
|
|
77
|
+
account: Partial<{
|
|
78
|
+
name: string;
|
|
79
|
+
id: number;
|
|
80
|
+
sort: number;
|
|
81
|
+
}>[];
|
|
82
|
+
user: Partial<{
|
|
83
|
+
name: string;
|
|
84
|
+
id: number;
|
|
85
|
+
sort: number;
|
|
86
|
+
accountId: number;
|
|
87
|
+
role: import(".prisma/client").$Enums.Role;
|
|
88
|
+
clicks: number;
|
|
89
|
+
deleted: boolean;
|
|
90
|
+
uniqueField: string;
|
|
91
|
+
age: number;
|
|
92
|
+
}>[];
|
|
93
|
+
answers: Partial<{
|
|
94
|
+
id: number;
|
|
95
|
+
title: string;
|
|
96
|
+
}>[];
|
|
97
|
+
element: Partial<{
|
|
98
|
+
value: string;
|
|
99
|
+
userId: number;
|
|
100
|
+
e_id: number;
|
|
101
|
+
json: Prisma.JsonValue;
|
|
102
|
+
}>[];
|
|
103
|
+
stripe: Partial<{
|
|
104
|
+
id: number;
|
|
105
|
+
sort: number;
|
|
106
|
+
accountId: number;
|
|
107
|
+
customerId: string;
|
|
108
|
+
active: boolean;
|
|
109
|
+
}>[];
|
|
110
|
+
userAnswers: Partial<{
|
|
111
|
+
value: string;
|
|
112
|
+
answerId: number;
|
|
113
|
+
userId: number;
|
|
114
|
+
}>[];
|
|
115
|
+
document: Partial<{
|
|
116
|
+
name: string;
|
|
117
|
+
id: string;
|
|
118
|
+
}>[];
|
|
119
|
+
post: Partial<{
|
|
120
|
+
id: number;
|
|
121
|
+
title: string;
|
|
122
|
+
published: boolean;
|
|
123
|
+
authorId: number;
|
|
124
|
+
updated: Date;
|
|
125
|
+
created: Date;
|
|
126
|
+
}>[];
|
|
127
|
+
pet: Partial<{
|
|
128
|
+
name: string;
|
|
129
|
+
id: number;
|
|
130
|
+
ownerId: number;
|
|
131
|
+
}>[];
|
|
132
|
+
toy: Partial<{
|
|
133
|
+
name: string;
|
|
134
|
+
id: number;
|
|
135
|
+
ownerId: number;
|
|
136
|
+
}>[];
|
|
137
|
+
transaction: Partial<{
|
|
138
|
+
id: string;
|
|
139
|
+
initiator: string;
|
|
140
|
+
}>[];
|
|
141
|
+
dbGeneratedId: Partial<{
|
|
142
|
+
id: string;
|
|
143
|
+
}>[];
|
|
144
|
+
}>>) => void;
|
|
76
145
|
$clear: () => void;
|
|
77
146
|
};
|
|
@@ -40,6 +40,8 @@ function createMatch({ prisma, getFieldRelationshipWhere, getDelegateForFieldNam
|
|
|
40
40
|
if (info?.relationName) {
|
|
41
41
|
const childName = (0, fieldHelpers_1.getCamelCase)(info.type);
|
|
42
42
|
let childWhere = {};
|
|
43
|
+
let useIsFilter = false;
|
|
44
|
+
let useIsNotFilter = false;
|
|
43
45
|
if (filter.every) {
|
|
44
46
|
childWhere = filter.every;
|
|
45
47
|
}
|
|
@@ -49,6 +51,14 @@ function createMatch({ prisma, getFieldRelationshipWhere, getDelegateForFieldNam
|
|
|
49
51
|
else if (filter.none) {
|
|
50
52
|
childWhere = filter.none;
|
|
51
53
|
}
|
|
54
|
+
else if ("is" in filter) {
|
|
55
|
+
useIsFilter = true;
|
|
56
|
+
childWhere = filter.is === null ? {} : filter.is;
|
|
57
|
+
}
|
|
58
|
+
else if ("isNot" in filter) {
|
|
59
|
+
useIsNotFilter = true;
|
|
60
|
+
childWhere = filter.isNot === null ? {} : filter.isNot;
|
|
61
|
+
}
|
|
52
62
|
else {
|
|
53
63
|
childWhere = filter;
|
|
54
64
|
}
|
|
@@ -57,10 +67,37 @@ function createMatch({ prisma, getFieldRelationshipWhere, getDelegateForFieldNam
|
|
|
57
67
|
});
|
|
58
68
|
const delegate = getDelegateForFieldName(childName);
|
|
59
69
|
const joinWhere = getFieldRelationshipWhere(item, info, submodel);
|
|
70
|
+
if (useIsFilter) {
|
|
71
|
+
if (filter.is === null) {
|
|
72
|
+
if (!joinWhere)
|
|
73
|
+
return true;
|
|
74
|
+
const res = delegate.findMany({ where: joinWhere });
|
|
75
|
+
return res.length === 0;
|
|
76
|
+
}
|
|
77
|
+
if (!joinWhere)
|
|
78
|
+
return false;
|
|
79
|
+
const res = delegate.findMany({
|
|
80
|
+
where: { AND: [childWhere, joinWhere] },
|
|
81
|
+
});
|
|
82
|
+
return res.length > 0;
|
|
83
|
+
}
|
|
84
|
+
if (useIsNotFilter) {
|
|
85
|
+
if (filter.isNot === null) {
|
|
86
|
+
if (!joinWhere)
|
|
87
|
+
return false;
|
|
88
|
+
const res = delegate.findMany({ where: joinWhere });
|
|
89
|
+
return res.length > 0;
|
|
90
|
+
}
|
|
91
|
+
if (!joinWhere)
|
|
92
|
+
return true;
|
|
93
|
+
const res = delegate.findMany({
|
|
94
|
+
where: { AND: [childWhere, joinWhere] },
|
|
95
|
+
});
|
|
96
|
+
return res.length === 0;
|
|
97
|
+
}
|
|
60
98
|
if (!joinWhere) {
|
|
61
99
|
return false;
|
|
62
100
|
}
|
|
63
|
-
// return true
|
|
64
101
|
const res = delegate.findMany({
|
|
65
102
|
where: {
|
|
66
103
|
AND: [
|
|
@@ -70,17 +107,12 @@ function createMatch({ prisma, getFieldRelationshipWhere, getDelegateForFieldNam
|
|
|
70
107
|
}
|
|
71
108
|
});
|
|
72
109
|
if (filter.every) {
|
|
73
|
-
// const all = data[childName].filter(
|
|
74
|
-
// matchFnc(getFieldRelationshipWhere(item, info)),
|
|
75
|
-
// )
|
|
76
110
|
const where = getFieldRelationshipWhere(item, info, model);
|
|
77
111
|
if (!where)
|
|
78
112
|
return false;
|
|
79
113
|
const all = delegate.findMany({
|
|
80
114
|
where,
|
|
81
115
|
});
|
|
82
|
-
// For "every": all related records must match the condition
|
|
83
|
-
// If no related records exist, "every" is vacuously true
|
|
84
116
|
if (all.length === 0)
|
|
85
117
|
return true;
|
|
86
118
|
return res.length === all.length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-mock",
|
|
3
|
-
"version": "1.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.3",
|
|
4
4
|
"description": "Mock prisma for unit testing database",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -45,11 +45,13 @@
|
|
|
45
45
|
"preversion": "jest && tsc",
|
|
46
46
|
"lint": "tsc --noEmit",
|
|
47
47
|
"build": "tsc",
|
|
48
|
-
"test": "jest",
|
|
48
|
+
"test": "prisma generate && npm run generate:mongodb && jest",
|
|
49
49
|
"generate": "prisma generate",
|
|
50
|
+
"generate:mongodb": "DATABASE_URL_MONGODB=mongodb://localhost:27017/test prisma generate --schema=prisma/schema-mongodb.prisma",
|
|
50
51
|
"release": "yarn build && changeset publish && git push --follow-tags",
|
|
51
52
|
"watch": "tsc --watch",
|
|
52
53
|
"test:postgres": "env-cmd -e postgres jest --maxWorkers=1",
|
|
54
|
+
"test:mongodb": "npm run generate:mongodb && jest __tests__/mongodb.test.ts",
|
|
53
55
|
"changeset": "changeset",
|
|
54
56
|
"changeset:add": "changeset add",
|
|
55
57
|
"changeset:validate:ci": "changeset status --since=origin/main --verbose"
|