@sqldoc/ns-validate 0.0.1 → 0.0.2
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/package.json +4 -3
- package/src/__tests__/validate.test.ts +0 -272
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sqldoc/ns-validate",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"description": "Validation namespace for sqldoc -- generates CHECK constraints",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
"types": "./src/index.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"src",
|
|
17
|
+
"!src/__tests__",
|
|
17
18
|
"package.json"
|
|
18
19
|
],
|
|
19
20
|
"peerDependencies": {
|
|
20
|
-
"@sqldoc/core": "0.0.
|
|
21
|
+
"@sqldoc/core": "0.0.2"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"typescript": "^5.9.3",
|
|
24
25
|
"vitest": "^4.1.0",
|
|
25
|
-
"@sqldoc/core": "0.0.
|
|
26
|
+
"@sqldoc/core": "0.0.2"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"test": "vitest run"
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import type { TagContext } from '@sqldoc/core'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import plugin from '../index'
|
|
4
|
-
|
|
5
|
-
function makeCtx(overrides: Partial<TagContext> = {}): TagContext {
|
|
6
|
-
return {
|
|
7
|
-
target: 'column',
|
|
8
|
-
objectName: 'users',
|
|
9
|
-
columnName: 'username',
|
|
10
|
-
columnType: 'text',
|
|
11
|
-
tag: { name: 'check', args: {} },
|
|
12
|
-
namespaceTags: [],
|
|
13
|
-
siblingTags: [],
|
|
14
|
-
fileTags: [],
|
|
15
|
-
astNode: null,
|
|
16
|
-
fileStatements: [],
|
|
17
|
-
config: {},
|
|
18
|
-
filePath: 'test.sql',
|
|
19
|
-
...overrides,
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('ns-validate plugin', () => {
|
|
24
|
-
it('exports apiVersion === 1', () => {
|
|
25
|
-
expect(plugin.apiVersion).toBe(1)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('exports name === "validate"', () => {
|
|
29
|
-
expect(plugin.name).toBe('validate')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('has all tag entries', () => {
|
|
33
|
-
expect(plugin.tags).toHaveProperty('check')
|
|
34
|
-
expect(plugin.tags).toHaveProperty('notEmpty')
|
|
35
|
-
expect(plugin.tags).toHaveProperty('range')
|
|
36
|
-
expect(plugin.tags).toHaveProperty('length')
|
|
37
|
-
expect(plugin.tags).toHaveProperty('pattern')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('onTag', () => {
|
|
41
|
-
it('@validate.check generates ALTER TABLE ADD CONSTRAINT with CHECK', () => {
|
|
42
|
-
const ctx = makeCtx({
|
|
43
|
-
tag: { name: 'check', args: ['length(username) >= 3'] },
|
|
44
|
-
})
|
|
45
|
-
const result = plugin.onTag!(ctx) as any
|
|
46
|
-
expect(result.sql).toEqual([
|
|
47
|
-
{
|
|
48
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_username_check" CHECK (length(username) >= 3);',
|
|
49
|
-
},
|
|
50
|
-
])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('@validate.notEmpty generates CHECK with length(trim(col)) > 0', () => {
|
|
54
|
-
const ctx = makeCtx({
|
|
55
|
-
columnName: 'email',
|
|
56
|
-
tag: { name: 'notEmpty', args: {} },
|
|
57
|
-
})
|
|
58
|
-
const result = plugin.onTag!(ctx) as any
|
|
59
|
-
expect(result.sql).toEqual([
|
|
60
|
-
{
|
|
61
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_email_not_empty" CHECK (length(trim("email")) > 0);',
|
|
62
|
-
},
|
|
63
|
-
])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('@validate.range generates CHECK with min/max bounds', () => {
|
|
67
|
-
const ctx = makeCtx({
|
|
68
|
-
columnName: 'age',
|
|
69
|
-
columnType: 'integer',
|
|
70
|
-
tag: { name: 'range', args: { min: 0, max: 150 } },
|
|
71
|
-
})
|
|
72
|
-
const result = plugin.onTag!(ctx) as any
|
|
73
|
-
expect(result.sql).toEqual([
|
|
74
|
-
{
|
|
75
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_age_range" CHECK ("age" >= 0 AND "age" <= 150);',
|
|
76
|
-
},
|
|
77
|
-
])
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('@validate.length with both min and max generates combined CHECK', () => {
|
|
81
|
-
const ctx = makeCtx({
|
|
82
|
-
tag: { name: 'length', args: { min: 3, max: 50 } },
|
|
83
|
-
})
|
|
84
|
-
const result = plugin.onTag!(ctx) as any
|
|
85
|
-
expect(result.sql).toEqual([
|
|
86
|
-
{
|
|
87
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_username_length" CHECK (length("username") >= 3 AND length("username") <= 50);',
|
|
88
|
-
},
|
|
89
|
-
])
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('@validate.length with only min generates >= CHECK', () => {
|
|
93
|
-
const ctx = makeCtx({
|
|
94
|
-
tag: { name: 'length', args: { min: 3 } },
|
|
95
|
-
})
|
|
96
|
-
const result = plugin.onTag!(ctx) as any
|
|
97
|
-
expect(result.sql).toEqual([
|
|
98
|
-
{
|
|
99
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_username_length" CHECK (length("username") >= 3);',
|
|
100
|
-
},
|
|
101
|
-
])
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('@validate.length with only max generates <= CHECK', () => {
|
|
105
|
-
const ctx = makeCtx({
|
|
106
|
-
tag: { name: 'length', args: { max: 255 } },
|
|
107
|
-
})
|
|
108
|
-
const result = plugin.onTag!(ctx) as any
|
|
109
|
-
expect(result.sql).toEqual([
|
|
110
|
-
{
|
|
111
|
-
sql: 'ALTER TABLE "users" ADD CONSTRAINT "users_username_length" CHECK (length("username") <= 255);',
|
|
112
|
-
},
|
|
113
|
-
])
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('@validate.pattern generates CHECK with regex operator', () => {
|
|
117
|
-
const ctx = makeCtx({
|
|
118
|
-
columnName: 'email',
|
|
119
|
-
tag: { name: 'pattern', args: ['^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'] },
|
|
120
|
-
})
|
|
121
|
-
const result = plugin.onTag!(ctx) as any
|
|
122
|
-
expect(result.sql).toEqual([
|
|
123
|
-
{
|
|
124
|
-
sql: `ALTER TABLE "users" ADD CONSTRAINT "users_email_pattern" CHECK ("email" ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$');`,
|
|
125
|
-
},
|
|
126
|
-
])
|
|
127
|
-
})
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
describe('validation', () => {
|
|
131
|
-
it('@validate.notEmpty warns on non-text column', () => {
|
|
132
|
-
const validate = plugin.tags.notEmpty.validate!
|
|
133
|
-
const result = validate({
|
|
134
|
-
target: 'column',
|
|
135
|
-
lines: [],
|
|
136
|
-
siblingTags: [],
|
|
137
|
-
fileTags: [],
|
|
138
|
-
argValues: {},
|
|
139
|
-
columnName: 'age',
|
|
140
|
-
columnType: 'integer',
|
|
141
|
-
objectName: 'users',
|
|
142
|
-
})
|
|
143
|
-
expect(result).toEqual({
|
|
144
|
-
message: 'notEmpty is typically used on text columns',
|
|
145
|
-
severity: 'warning',
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('@validate.notEmpty does not warn on text column', () => {
|
|
150
|
-
const validate = plugin.tags.notEmpty.validate!
|
|
151
|
-
const result = validate({
|
|
152
|
-
target: 'column',
|
|
153
|
-
lines: [],
|
|
154
|
-
siblingTags: [],
|
|
155
|
-
fileTags: [],
|
|
156
|
-
argValues: {},
|
|
157
|
-
columnName: 'name',
|
|
158
|
-
columnType: 'text',
|
|
159
|
-
objectName: 'users',
|
|
160
|
-
})
|
|
161
|
-
expect(result).toBeUndefined()
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
it('@validate.notEmpty does not warn on varchar column', () => {
|
|
165
|
-
const validate = plugin.tags.notEmpty.validate!
|
|
166
|
-
const result = validate({
|
|
167
|
-
target: 'column',
|
|
168
|
-
lines: [],
|
|
169
|
-
siblingTags: [],
|
|
170
|
-
fileTags: [],
|
|
171
|
-
argValues: {},
|
|
172
|
-
columnName: 'name',
|
|
173
|
-
columnType: 'varchar(255)',
|
|
174
|
-
objectName: 'users',
|
|
175
|
-
})
|
|
176
|
-
expect(result).toBeUndefined()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('@validate.range errors when min >= max', () => {
|
|
180
|
-
const validate = plugin.tags.range.validate!
|
|
181
|
-
const result = validate({
|
|
182
|
-
target: 'column',
|
|
183
|
-
lines: [],
|
|
184
|
-
siblingTags: [],
|
|
185
|
-
fileTags: [],
|
|
186
|
-
argValues: { min: 10, max: 5 },
|
|
187
|
-
columnName: 'age',
|
|
188
|
-
columnType: 'integer',
|
|
189
|
-
objectName: 'users',
|
|
190
|
-
})
|
|
191
|
-
expect(result).toBe('min must be less than max')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('@validate.range errors when min equals max', () => {
|
|
195
|
-
const validate = plugin.tags.range.validate!
|
|
196
|
-
const result = validate({
|
|
197
|
-
target: 'column',
|
|
198
|
-
lines: [],
|
|
199
|
-
siblingTags: [],
|
|
200
|
-
fileTags: [],
|
|
201
|
-
argValues: { min: 5, max: 5 },
|
|
202
|
-
columnName: 'age',
|
|
203
|
-
columnType: 'integer',
|
|
204
|
-
objectName: 'users',
|
|
205
|
-
})
|
|
206
|
-
expect(result).toBe('min must be less than max')
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
it('@validate.range passes with valid min < max', () => {
|
|
210
|
-
const validate = plugin.tags.range.validate!
|
|
211
|
-
const result = validate({
|
|
212
|
-
target: 'column',
|
|
213
|
-
lines: [],
|
|
214
|
-
siblingTags: [],
|
|
215
|
-
fileTags: [],
|
|
216
|
-
argValues: { min: 0, max: 100 },
|
|
217
|
-
columnName: 'age',
|
|
218
|
-
columnType: 'integer',
|
|
219
|
-
objectName: 'users',
|
|
220
|
-
})
|
|
221
|
-
expect(result).toBeUndefined()
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
it('@validate.length errors when neither min nor max provided', () => {
|
|
225
|
-
const validate = plugin.tags.length.validate!
|
|
226
|
-
const result = validate({
|
|
227
|
-
target: 'column',
|
|
228
|
-
lines: [],
|
|
229
|
-
siblingTags: [],
|
|
230
|
-
fileTags: [],
|
|
231
|
-
argValues: {},
|
|
232
|
-
columnName: 'name',
|
|
233
|
-
columnType: 'text',
|
|
234
|
-
objectName: 'users',
|
|
235
|
-
})
|
|
236
|
-
expect(result).toBe('at least one of min or max is required')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('@validate.length errors when min >= max', () => {
|
|
240
|
-
const validate = plugin.tags.length.validate!
|
|
241
|
-
const result = validate({
|
|
242
|
-
target: 'column',
|
|
243
|
-
lines: [],
|
|
244
|
-
siblingTags: [],
|
|
245
|
-
fileTags: [],
|
|
246
|
-
argValues: { min: 50, max: 10 },
|
|
247
|
-
columnName: 'name',
|
|
248
|
-
columnType: 'text',
|
|
249
|
-
objectName: 'users',
|
|
250
|
-
})
|
|
251
|
-
expect(result).toBe('min must be less than max')
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('@validate.length warns on non-text column', () => {
|
|
255
|
-
const validate = plugin.tags.length.validate!
|
|
256
|
-
const result = validate({
|
|
257
|
-
target: 'column',
|
|
258
|
-
lines: [],
|
|
259
|
-
siblingTags: [],
|
|
260
|
-
fileTags: [],
|
|
261
|
-
argValues: { min: 1, max: 100 },
|
|
262
|
-
columnName: 'count',
|
|
263
|
-
columnType: 'integer',
|
|
264
|
-
objectName: 'users',
|
|
265
|
-
})
|
|
266
|
-
expect(result).toEqual({
|
|
267
|
-
message: 'length check is typically used on text columns',
|
|
268
|
-
severity: 'warning',
|
|
269
|
-
})
|
|
270
|
-
})
|
|
271
|
-
})
|
|
272
|
-
})
|