@simtlix/simfinity-js 2.0.2 → 2.2.0
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/AGGREGATION_CHANGES_SUMMARY.md +235 -0
- package/AGGREGATION_EXAMPLE.md +567 -0
- package/README.md +415 -3
- package/package.json +1 -1
- package/src/index.js +345 -0
- package/src/scalars.js +188 -0
- package/src/validators.js +250 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -10
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -10
- package/.github/workflows/master.yml +0 -19
- package/.github/workflows/publish.yml +0 -45
- package/.github/workflows/release.yml +0 -65
- package/BACKUP_README.md +0 -26
- package/README_INDEX_EXAMPLE.md +0 -252
- package/simtlix-simfinity-js-1.9.1.tgz +0 -0
- package/tests/objectid-indexes.test.js +0 -215
- package/tests/prevent-collection-creation.test.js +0 -67
- package/tests/scalar-naming.test.js +0 -125
- package/tests/validated-scalar.test.js +0 -172
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe, test, expect, beforeEach, afterEach, vi,
|
|
3
|
-
} from 'vitest';
|
|
4
|
-
import mongoose from 'mongoose';
|
|
5
|
-
import { GraphQLObjectType, GraphQLString, GraphQLID, GraphQLList } from 'graphql';
|
|
6
|
-
import * as simfinity from '../src/index.js';
|
|
7
|
-
|
|
8
|
-
describe('ObjectId Index Creation', () => {
|
|
9
|
-
let indexSpy;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Spy on the index method of mongoose Schema
|
|
13
|
-
indexSpy = vi.spyOn(mongoose.Schema.prototype, 'index').mockImplementation(() => {});
|
|
14
|
-
// Prevent collection creation to avoid database connection issues
|
|
15
|
-
simfinity.preventCreatingCollection(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
// Restore the original implementation
|
|
20
|
-
indexSpy.mockRestore();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('should create index for direct ObjectId field', () => {
|
|
24
|
-
const TestType = new GraphQLObjectType({
|
|
25
|
-
name: 'TestTypeWithObjectId',
|
|
26
|
-
fields: () => ({
|
|
27
|
-
id: { type: GraphQLID },
|
|
28
|
-
name: { type: GraphQLString },
|
|
29
|
-
userId: { type: GraphQLID },
|
|
30
|
-
}),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
simfinity.connect(null, TestType, 'testTypeWithObjectId', 'testTypesWithObjectId');
|
|
34
|
-
simfinity.createSchema();
|
|
35
|
-
|
|
36
|
-
// Should create indexes for both id and userId fields
|
|
37
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
38
|
-
expect(indexSpy).toHaveBeenCalledWith({ userId: 1 });
|
|
39
|
-
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('should create index for embedded ObjectId field', () => {
|
|
43
|
-
const EmbeddedType = new GraphQLObjectType({
|
|
44
|
-
name: 'EmbeddedType',
|
|
45
|
-
fields: () => ({
|
|
46
|
-
embeddedId: { type: GraphQLID },
|
|
47
|
-
embeddedName: { type: GraphQLString },
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const TestType = new GraphQLObjectType({
|
|
52
|
-
name: 'TestTypeWithEmbedded',
|
|
53
|
-
fields: () => ({
|
|
54
|
-
id: { type: GraphQLID },
|
|
55
|
-
name: { type: GraphQLString },
|
|
56
|
-
embedded: {
|
|
57
|
-
type: EmbeddedType,
|
|
58
|
-
extensions: {
|
|
59
|
-
relation: { embedded: true },
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
}),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Register the embedded type first
|
|
66
|
-
simfinity.addNoEndpointType(EmbeddedType);
|
|
67
|
-
simfinity.connect(null, TestType, 'testTypeWithEmbedded', 'testTypesWithEmbedded');
|
|
68
|
-
simfinity.createSchema();
|
|
69
|
-
|
|
70
|
-
// Should create indexes for id and embedded.embeddedId fields
|
|
71
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
72
|
-
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.embeddedId': 1 });
|
|
73
|
-
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('should create index for nested embedded ObjectId field', () => {
|
|
77
|
-
const DeepEmbeddedType = new GraphQLObjectType({
|
|
78
|
-
name: 'DeepEmbeddedType',
|
|
79
|
-
fields: () => ({
|
|
80
|
-
deepId: { type: GraphQLID },
|
|
81
|
-
deepName: { type: GraphQLString },
|
|
82
|
-
}),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const EmbeddedType = new GraphQLObjectType({
|
|
86
|
-
name: 'EmbeddedTypeWithDeep',
|
|
87
|
-
fields: () => ({
|
|
88
|
-
embeddedId: { type: GraphQLID },
|
|
89
|
-
embeddedName: { type: GraphQLString },
|
|
90
|
-
deep: {
|
|
91
|
-
type: DeepEmbeddedType,
|
|
92
|
-
extensions: {
|
|
93
|
-
relation: { embedded: true },
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
const TestType = new GraphQLObjectType({
|
|
100
|
-
name: 'TestTypeWithNestedEmbedded',
|
|
101
|
-
fields: () => ({
|
|
102
|
-
id: { type: GraphQLID },
|
|
103
|
-
name: { type: GraphQLString },
|
|
104
|
-
embedded: {
|
|
105
|
-
type: EmbeddedType,
|
|
106
|
-
extensions: {
|
|
107
|
-
relation: { embedded: true },
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
}),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Register the embedded types first
|
|
114
|
-
simfinity.addNoEndpointType(DeepEmbeddedType);
|
|
115
|
-
simfinity.addNoEndpointType(EmbeddedType);
|
|
116
|
-
simfinity.connect(null, TestType, 'testTypeWithNestedEmbedded', 'testTypesWithNestedEmbedded');
|
|
117
|
-
simfinity.createSchema();
|
|
118
|
-
|
|
119
|
-
// Should create indexes for all ObjectId fields at all levels
|
|
120
|
-
expect(indexSpy).toHaveBeenCalledWith({ embeddedId: 1 }); // EmbeddedType
|
|
121
|
-
expect(indexSpy).toHaveBeenCalledWith({ 'deep.deepId': 1 }); // DeepEmbeddedType
|
|
122
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // TestType
|
|
123
|
-
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.embeddedId': 1 }); // embedded field in TestType
|
|
124
|
-
expect(indexSpy).toHaveBeenCalledWith({ 'embedded.deep.deepId': 1 }); // nested embedded field in TestType
|
|
125
|
-
expect(indexSpy).toHaveBeenCalledTimes(5);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('should create index for array of embedded objects with ObjectId', () => {
|
|
129
|
-
const EmbeddedType = new GraphQLObjectType({
|
|
130
|
-
name: 'EmbeddedTypeForArray',
|
|
131
|
-
fields: () => ({
|
|
132
|
-
embeddedId: { type: GraphQLID },
|
|
133
|
-
embeddedName: { type: GraphQLString },
|
|
134
|
-
}),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const TestType = new GraphQLObjectType({
|
|
138
|
-
name: 'TestTypeWithEmbeddedArray',
|
|
139
|
-
fields: () => ({
|
|
140
|
-
id: { type: GraphQLID },
|
|
141
|
-
name: { type: GraphQLString },
|
|
142
|
-
embeddedArray: {
|
|
143
|
-
type: new GraphQLList(EmbeddedType),
|
|
144
|
-
extensions: {
|
|
145
|
-
relation: { embedded: true },
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
}),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Register the embedded type first
|
|
152
|
-
simfinity.addNoEndpointType(EmbeddedType);
|
|
153
|
-
simfinity.connect(null, TestType, 'testTypeWithEmbeddedArray', 'testTypesWithEmbeddedArray');
|
|
154
|
-
simfinity.createSchema();
|
|
155
|
-
|
|
156
|
-
// Should create indexes for id and embeddedArray.embeddedId fields
|
|
157
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
158
|
-
expect(indexSpy).toHaveBeenCalledWith({ 'embeddedArray.embeddedId': 1 });
|
|
159
|
-
expect(indexSpy).toHaveBeenCalledTimes(2);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test('should not create index for non-ObjectId fields', () => {
|
|
163
|
-
const TestType = new GraphQLObjectType({
|
|
164
|
-
name: 'TestTypeWithoutObjectId',
|
|
165
|
-
fields: () => ({
|
|
166
|
-
id: { type: GraphQLID },
|
|
167
|
-
name: { type: GraphQLString },
|
|
168
|
-
age: { type: GraphQLString },
|
|
169
|
-
active: { type: GraphQLString },
|
|
170
|
-
}),
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
simfinity.connect(null, TestType, 'testTypeWithoutObjectId', 'testTypesWithoutObjectId');
|
|
174
|
-
simfinity.createSchema();
|
|
175
|
-
|
|
176
|
-
// Should only create index for id field (ObjectId), not for other fields
|
|
177
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 });
|
|
178
|
-
expect(indexSpy).toHaveBeenCalledTimes(1);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test('should create index for relationship fields (non-embedded)', () => {
|
|
182
|
-
const DepartmentType = new GraphQLObjectType({
|
|
183
|
-
name: 'DepartmentType',
|
|
184
|
-
fields: () => ({
|
|
185
|
-
id: { type: GraphQLID },
|
|
186
|
-
name: { type: GraphQLString },
|
|
187
|
-
}),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const UserType = new GraphQLObjectType({
|
|
191
|
-
name: 'TestTypeWithRelationship',
|
|
192
|
-
fields: () => ({
|
|
193
|
-
id: { type: GraphQLID },
|
|
194
|
-
name: { type: GraphQLString },
|
|
195
|
-
department: {
|
|
196
|
-
type: DepartmentType,
|
|
197
|
-
extensions: {
|
|
198
|
-
relation: { embedded: false },
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
}),
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Register both types
|
|
205
|
-
simfinity.connect(null, DepartmentType, 'department', 'departments');
|
|
206
|
-
simfinity.connect(null, UserType, 'testTypeWithRelationship', 'testTypesWithRelationship');
|
|
207
|
-
simfinity.createSchema();
|
|
208
|
-
|
|
209
|
-
// Should create indexes for id fields in both types and the relationship field
|
|
210
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // DepartmentType
|
|
211
|
-
expect(indexSpy).toHaveBeenCalledWith({ id: 1 }); // UserType
|
|
212
|
-
expect(indexSpy).toHaveBeenCalledWith({ department: 1 }); // relationship field
|
|
213
|
-
expect(indexSpy).toHaveBeenCalledTimes(3);
|
|
214
|
-
});
|
|
215
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe, test, expect, beforeEach, afterEach, vi,
|
|
3
|
-
} from 'vitest';
|
|
4
|
-
import mongoose from 'mongoose';
|
|
5
|
-
import { GraphQLObjectType, GraphQLString, GraphQLID } from 'graphql';
|
|
6
|
-
import * as simfinity from '../src/index.js';
|
|
7
|
-
|
|
8
|
-
describe('preventCreatingCollection option', () => {
|
|
9
|
-
let createCollectionSpy;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Spy on the createCollection method of the mongoose model prototype
|
|
13
|
-
createCollectionSpy = vi.spyOn(mongoose.Model, 'createCollection').mockImplementation(() => Promise.resolve());
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
// Restore the original implementation
|
|
18
|
-
createCollectionSpy.mockRestore();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test('should create collection by default', () => {
|
|
22
|
-
const TestType = new GraphQLObjectType({
|
|
23
|
-
name: 'TestTypeDefault',
|
|
24
|
-
fields: () => ({
|
|
25
|
-
id: { type: GraphQLID },
|
|
26
|
-
name: { type: GraphQLString },
|
|
27
|
-
}),
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
simfinity.connect(null, TestType, 'testTypeDefault', 'testTypesDefault');
|
|
31
|
-
simfinity.createSchema(); // Models are now generated during schema creation
|
|
32
|
-
expect(createCollectionSpy).toHaveBeenCalledTimes(1);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('should NOT create collection when preventCreatingCollection is true', () => {
|
|
36
|
-
simfinity.preventCreatingCollection(true);
|
|
37
|
-
|
|
38
|
-
const TestType = new GraphQLObjectType({
|
|
39
|
-
name: 'TestTypePrevent',
|
|
40
|
-
fields: () => ({
|
|
41
|
-
id: { type: GraphQLID },
|
|
42
|
-
name: { type: GraphQLString },
|
|
43
|
-
}),
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
simfinity.connect(null, TestType, 'testTypePrevent', 'testTypesPrevent');
|
|
47
|
-
simfinity.createSchema(); // Models are now generated during schema creation
|
|
48
|
-
expect(createCollectionSpy).not.toHaveBeenCalled();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('should create collection when preventCreatingCollection is set back to false', () => {
|
|
52
|
-
simfinity.preventCreatingCollection(true); // first prevent
|
|
53
|
-
simfinity.preventCreatingCollection(false); // then allow
|
|
54
|
-
|
|
55
|
-
const TestType = new GraphQLObjectType({
|
|
56
|
-
name: 'TestTypeAllow',
|
|
57
|
-
fields: () => ({
|
|
58
|
-
id: { type: GraphQLID },
|
|
59
|
-
name: { type: GraphQLString },
|
|
60
|
-
}),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
simfinity.connect(null, TestType, 'testTypeAllow', 'testTypesAllow');
|
|
64
|
-
simfinity.createSchema(); // Models are now generated during schema creation
|
|
65
|
-
expect(createCollectionSpy).toHaveBeenCalledTimes(1);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe, test, expect,
|
|
3
|
-
} from 'vitest';
|
|
4
|
-
import { GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean, GraphQLID } from 'graphql';
|
|
5
|
-
import { createValidatedScalar } from '../src/index.js';
|
|
6
|
-
|
|
7
|
-
describe('Validated Scalar Naming Convention', () => {
|
|
8
|
-
test('should generate correct type names with base scalar type suffix', () => {
|
|
9
|
-
const EmailScalar = createValidatedScalar(
|
|
10
|
-
'Email',
|
|
11
|
-
'A valid email address',
|
|
12
|
-
GraphQLString,
|
|
13
|
-
(value) => {
|
|
14
|
-
if (!value.includes('@')) {
|
|
15
|
-
throw new Error('Invalid email format');
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const EpisodeNumberScalar = createValidatedScalar(
|
|
21
|
-
'EpisodeNumber',
|
|
22
|
-
'A valid episode number',
|
|
23
|
-
GraphQLInt,
|
|
24
|
-
(value) => {
|
|
25
|
-
if (value <= 0) {
|
|
26
|
-
throw new Error('Episode number must be positive');
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const RatingScalar = createValidatedScalar(
|
|
32
|
-
'Rating',
|
|
33
|
-
'A valid rating between 0 and 10',
|
|
34
|
-
GraphQLFloat,
|
|
35
|
-
(value) => {
|
|
36
|
-
if (value < 0 || value > 10) {
|
|
37
|
-
throw new Error('Rating must be between 0 and 10');
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const IsActiveScalar = createValidatedScalar(
|
|
43
|
-
'IsActive',
|
|
44
|
-
'A boolean indicating if something is active',
|
|
45
|
-
GraphQLBoolean,
|
|
46
|
-
(value) => {
|
|
47
|
-
// Boolean validation is usually not needed, but this is for testing
|
|
48
|
-
if (typeof value !== 'boolean') {
|
|
49
|
-
throw new Error('Must be a boolean value');
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const CustomIdScalar = createValidatedScalar(
|
|
55
|
-
'CustomId',
|
|
56
|
-
'A custom ID with specific format',
|
|
57
|
-
GraphQLID,
|
|
58
|
-
(value) => {
|
|
59
|
-
if (!value.startsWith('CUST_')) {
|
|
60
|
-
throw new Error('Custom ID must start with CUST_');
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Test the naming convention
|
|
66
|
-
expect(EmailScalar.name).toBe('Email_String');
|
|
67
|
-
expect(EpisodeNumberScalar.name).toBe('EpisodeNumber_Int');
|
|
68
|
-
expect(RatingScalar.name).toBe('Rating_Float');
|
|
69
|
-
expect(IsActiveScalar.name).toBe('IsActive_Boolean');
|
|
70
|
-
expect(CustomIdScalar.name).toBe('CustomId_ID');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('should maintain baseScalarType property', () => {
|
|
74
|
-
const EmailScalar = createValidatedScalar(
|
|
75
|
-
'Email',
|
|
76
|
-
'A valid email address',
|
|
77
|
-
GraphQLString,
|
|
78
|
-
(value) => {
|
|
79
|
-
if (!value.includes('@')) {
|
|
80
|
-
throw new Error('Invalid email format');
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
expect(EmailScalar.baseScalarType).toBe(GraphQLString);
|
|
86
|
-
expect(EmailScalar.name).toBe('Email_String');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('should work with validation functions', () => {
|
|
90
|
-
const EpisodeNumberScalar = createValidatedScalar(
|
|
91
|
-
'EpisodeNumber',
|
|
92
|
-
'A valid episode number',
|
|
93
|
-
GraphQLInt,
|
|
94
|
-
(value) => {
|
|
95
|
-
if (value <= 0) {
|
|
96
|
-
throw new Error('Episode number must be positive');
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// Test valid value
|
|
102
|
-
expect(() => EpisodeNumberScalar.serialize(5)).not.toThrow();
|
|
103
|
-
expect(EpisodeNumberScalar.serialize(5)).toBe(5);
|
|
104
|
-
|
|
105
|
-
// Test invalid value
|
|
106
|
-
expect(() => EpisodeNumberScalar.serialize(0)).toThrow('Episode number must be positive');
|
|
107
|
-
expect(() => EpisodeNumberScalar.serialize(-1)).toThrow('Episode number must be positive');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('should generate error messages with correct type names', () => {
|
|
111
|
-
const EpisodeNumberScalar = createValidatedScalar(
|
|
112
|
-
'EpisodeNumber',
|
|
113
|
-
'A valid episode number',
|
|
114
|
-
GraphQLInt,
|
|
115
|
-
(value) => {
|
|
116
|
-
if (value <= 0) {
|
|
117
|
-
throw new Error('Episode number must be positive');
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// The error message should include the full type name
|
|
123
|
-
expect(() => EpisodeNumberScalar.serialize(0)).toThrow('Episode number must be positive');
|
|
124
|
-
});
|
|
125
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe, test, expect, beforeAll,
|
|
3
|
-
} from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLID, GraphQLList, GraphQLNonNull,
|
|
6
|
-
} from 'graphql';
|
|
7
|
-
import { createValidatedScalar } from '../src/index.js';
|
|
8
|
-
import * as simfinity from '../src/index.js';
|
|
9
|
-
|
|
10
|
-
describe('Custom Validated Scalar Types', () => {
|
|
11
|
-
let EmailScalar;
|
|
12
|
-
let PositiveIntScalar;
|
|
13
|
-
let PhoneScalar;
|
|
14
|
-
let UserType;
|
|
15
|
-
|
|
16
|
-
beforeAll(() => {
|
|
17
|
-
simfinity.preventCreatingCollection(true);
|
|
18
|
-
// Create custom validated scalar types
|
|
19
|
-
EmailScalar = createValidatedScalar(
|
|
20
|
-
'Email',
|
|
21
|
-
'A valid email address',
|
|
22
|
-
GraphQLString,
|
|
23
|
-
(value) => {
|
|
24
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
25
|
-
if (!emailRegex.test(value)) {
|
|
26
|
-
throw new Error('Invalid email format');
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
PositiveIntScalar = createValidatedScalar(
|
|
32
|
-
'PositiveInt',
|
|
33
|
-
'A positive integer',
|
|
34
|
-
GraphQLInt,
|
|
35
|
-
(value) => {
|
|
36
|
-
if (value <= 0) {
|
|
37
|
-
throw new Error('Value must be positive');
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
PhoneScalar = createValidatedScalar(
|
|
43
|
-
'Phone',
|
|
44
|
-
'A valid phone number',
|
|
45
|
-
GraphQLString,
|
|
46
|
-
(value) => {
|
|
47
|
-
const phoneRegex = /^\+?[\d\s\-()]+$/;
|
|
48
|
-
if (!phoneRegex.test(value)) {
|
|
49
|
-
throw new Error('Invalid phone number format');
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
// Create a test type with custom scalars
|
|
55
|
-
UserType = new GraphQLObjectType({
|
|
56
|
-
name: 'User',
|
|
57
|
-
fields: () => ({
|
|
58
|
-
id: { type: GraphQLID },
|
|
59
|
-
name: { type: GraphQLString },
|
|
60
|
-
email: { type: EmailScalar },
|
|
61
|
-
age: { type: PositiveIntScalar },
|
|
62
|
-
phone: { type: PhoneScalar },
|
|
63
|
-
emails: { type: new GraphQLList(EmailScalar) },
|
|
64
|
-
requiredEmail: { type: new GraphQLNonNull(EmailScalar) },
|
|
65
|
-
ages: { type: new GraphQLList(PositiveIntScalar) },
|
|
66
|
-
}),
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('createValidatedScalar function', () => {
|
|
71
|
-
test('should create a valid scalar type with baseScalarType property', () => {
|
|
72
|
-
expect(EmailScalar).toBeDefined();
|
|
73
|
-
expect(EmailScalar.name).toBe('Email_String');
|
|
74
|
-
expect(EmailScalar.baseScalarType).toBe(GraphQLString);
|
|
75
|
-
expect(EmailScalar.serialize).toBeDefined();
|
|
76
|
-
expect(EmailScalar.parseValue).toBeDefined();
|
|
77
|
-
expect(EmailScalar.parseLiteral).toBeDefined();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('should validate baseScalarType parameter', () => {
|
|
81
|
-
expect(() => {
|
|
82
|
-
createValidatedScalar('Test', 'Test', null, () => {});
|
|
83
|
-
}).toThrow('baseScalarType is required');
|
|
84
|
-
|
|
85
|
-
expect(() => {
|
|
86
|
-
createValidatedScalar('Test', 'Test', 'not a scalar', () => {});
|
|
87
|
-
}).toThrow('baseScalarType must be a valid GraphQL scalar type');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('should handle different base scalar types', () => {
|
|
91
|
-
expect(PositiveIntScalar.baseScalarType).toBe(GraphQLInt);
|
|
92
|
-
expect(PhoneScalar.baseScalarType).toBe(GraphQLString);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('Custom scalar validation', () => {
|
|
97
|
-
test('should validate email format correctly', () => {
|
|
98
|
-
expect(() => EmailScalar.serialize('test@example.com')).not.toThrow();
|
|
99
|
-
expect(() => EmailScalar.serialize('invalid-email')).toThrow('Invalid email format');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test('should validate positive integers correctly', () => {
|
|
103
|
-
expect(() => PositiveIntScalar.serialize(5)).not.toThrow();
|
|
104
|
-
expect(() => PositiveIntScalar.serialize(0)).toThrow('Value must be positive');
|
|
105
|
-
expect(() => PositiveIntScalar.serialize(-1)).toThrow('Value must be positive');
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('should validate phone numbers correctly', () => {
|
|
109
|
-
expect(() => PhoneScalar.serialize('+1-555-123-4567')).not.toThrow();
|
|
110
|
-
expect(() => PhoneScalar.serialize('555-123-4567')).not.toThrow();
|
|
111
|
-
expect(() => PhoneScalar.serialize('invalid phone')).toThrow('Invalid phone number format');
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('Schema generation with custom scalars', () => {
|
|
116
|
-
let UserModel;
|
|
117
|
-
|
|
118
|
-
beforeAll(() => {
|
|
119
|
-
simfinity.connect(null, UserType, 'user', 'users');
|
|
120
|
-
simfinity.createSchema(); // Models are now generated during schema creation
|
|
121
|
-
UserModel = simfinity.getModel(UserType);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('should generate schema with correct types for custom scalars', () => {
|
|
125
|
-
const schema = UserModel.schema.obj;
|
|
126
|
-
|
|
127
|
-
// Test individual fields
|
|
128
|
-
expect(schema.email).toBe(String);
|
|
129
|
-
expect(schema.age).toBe(Number);
|
|
130
|
-
expect(schema.phone).toBe(String);
|
|
131
|
-
|
|
132
|
-
// Test array fields
|
|
133
|
-
expect(Array.isArray(schema.emails)).toBe(true);
|
|
134
|
-
expect(schema.emails[0]).toBe(String);
|
|
135
|
-
expect(Array.isArray(schema.ages)).toBe(true);
|
|
136
|
-
expect(schema.ages[0]).toBe(Number);
|
|
137
|
-
|
|
138
|
-
// Test required fields
|
|
139
|
-
expect(schema.requiredEmail).toBe(String);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('should preserve unique constraints', () => {
|
|
143
|
-
const UserWithUniqueType = new GraphQLObjectType({
|
|
144
|
-
name: 'UserWithUnique',
|
|
145
|
-
fields: () => ({
|
|
146
|
-
id: { type: GraphQLID },
|
|
147
|
-
email: {
|
|
148
|
-
type: EmailScalar,
|
|
149
|
-
extensions: { unique: true },
|
|
150
|
-
},
|
|
151
|
-
}),
|
|
152
|
-
});
|
|
153
|
-
simfinity.connect(null, UserWithUniqueType, 'userWithUnique', 'usersWithUnique');
|
|
154
|
-
simfinity.createSchema(); // Models are now generated during schema creation
|
|
155
|
-
const UserWithUniqueModel = simfinity.getModel(UserWithUniqueType);
|
|
156
|
-
const schema = UserWithUniqueModel.schema.obj;
|
|
157
|
-
|
|
158
|
-
expect(schema.email).toEqual({ type: String, unique: true });
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('GraphQL schema integration', () => {
|
|
163
|
-
test('should create valid GraphQL schema with custom scalars', () => {
|
|
164
|
-
const schema = simfinity.createSchema();
|
|
165
|
-
|
|
166
|
-
// The schema should be created without errors
|
|
167
|
-
expect(schema).toBeDefined();
|
|
168
|
-
expect(schema.getQueryType()).toBeDefined();
|
|
169
|
-
expect(schema.getMutationType()).toBeDefined();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
});
|