Skip to content

Commit 53dd517

Browse files
feat: add validation tests for external services configuration
1 parent cda698b commit 53dd517

File tree

2 files changed

+215
-5
lines changed

2 files changed

+215
-5
lines changed

src/core/validation/external-services.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ export function validateExternalServices(services: unknown[]): string[] {
3030
serviceNames.add(service.name)
3131
}
3232

33-
if (!('schema' in service) || !service.schema) {
34-
errors.push(`${prefix}.schema is required`)
35-
}
36-
3733
if (!('endpoint' in service) || typeof service.endpoint !== 'string') {
3834
errors.push(`${prefix}.endpoint is required and must be a string`)
3935
}
@@ -50,7 +46,7 @@ export function validateExternalServices(services: unknown[]): string[] {
5046
}
5147

5248
// Validate service name format (should be valid for file names and TypeScript)
53-
if ('name' in service && service.name && typeof service.name === 'string' && !/^[a-z]\w*$/i.test(service.name)) {
49+
if ('name' in service && typeof service.name === 'string' && !/^[a-z]\w*$/i.test(service.name)) {
5450
errors.push(`${prefix}.name "${service.name}" must be a valid identifier (letters, numbers, underscore, starting with letter)`)
5551
}
5652
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { validateExternalServices } from '../../../src/core/validation/external-services'
3+
4+
describe('validation/external-services', () => {
5+
describe('validateExternalServices', () => {
6+
it('should return no errors for empty array', () => {
7+
const errors = validateExternalServices([])
8+
expect(errors).toEqual([])
9+
})
10+
11+
it('should return no errors for valid configuration', () => {
12+
const errors = validateExternalServices([
13+
{
14+
name: 'github',
15+
endpoint: 'https://api.github.com/graphql',
16+
},
17+
])
18+
expect(errors).toEqual([])
19+
})
20+
21+
it('should return no errors when schema is provided', () => {
22+
const errors = validateExternalServices([
23+
{
24+
name: 'github',
25+
endpoint: 'https://api.github.com/graphql',
26+
schema: 'https://api.github.com/graphql',
27+
},
28+
])
29+
expect(errors).toEqual([])
30+
})
31+
32+
it('should return no errors when schema is omitted (uses endpoint for introspection)', () => {
33+
const errors = validateExternalServices([
34+
{
35+
name: 'ecommerce',
36+
endpoint: 'http://localhost:3123/api/graphql',
37+
downloadPath: './ecommerce.graphql',
38+
downloadSchema: 'always',
39+
documents: ['app/graphql/ecommerce/**/*.gql'],
40+
},
41+
])
42+
expect(errors).toEqual([])
43+
})
44+
45+
it('should return error if service is not an object', () => {
46+
const errors = validateExternalServices([null, 'invalid', 123, undefined])
47+
expect(errors).toContain('externalServices[0] must be an object')
48+
expect(errors).toContain('externalServices[1] must be an object')
49+
expect(errors).toContain('externalServices[2] must be an object')
50+
expect(errors).toContain('externalServices[3] must be an object')
51+
})
52+
53+
it('should return error if name is missing', () => {
54+
const errors = validateExternalServices([
55+
{
56+
endpoint: 'https://api.github.com/graphql',
57+
},
58+
])
59+
expect(errors).toContain('externalServices[0].name is required and must be a string')
60+
})
61+
62+
it('should return error if name is not a string', () => {
63+
const errors = validateExternalServices([
64+
{
65+
name: 123,
66+
endpoint: 'https://api.github.com/graphql',
67+
},
68+
])
69+
expect(errors).toContain('externalServices[0].name is required and must be a string')
70+
})
71+
72+
it('should return error if name is empty string', () => {
73+
const errors = validateExternalServices([
74+
{
75+
name: '',
76+
endpoint: 'https://api.github.com/graphql',
77+
},
78+
])
79+
expect(errors).toContain('externalServices[0].name "" must be a valid identifier (letters, numbers, underscore, starting with letter)')
80+
})
81+
82+
it('should return error for duplicate names', () => {
83+
const errors = validateExternalServices([
84+
{
85+
name: 'github',
86+
endpoint: 'https://api.github.com/graphql',
87+
},
88+
{
89+
name: 'github',
90+
endpoint: 'https://api.github.com/graphql2',
91+
},
92+
])
93+
expect(errors).toContain('externalServices[1].name "github" must be unique')
94+
})
95+
96+
it('should return error if endpoint is missing', () => {
97+
const errors = validateExternalServices([
98+
{
99+
name: 'github',
100+
},
101+
])
102+
expect(errors).toContain('externalServices[0].endpoint is required and must be a string')
103+
})
104+
105+
it('should return error if endpoint is not a string', () => {
106+
const errors = validateExternalServices([
107+
{
108+
name: 'github',
109+
endpoint: 123,
110+
},
111+
])
112+
expect(errors).toContain('externalServices[0].endpoint is required and must be a string')
113+
})
114+
115+
it('should return error if endpoint is not a valid URL', () => {
116+
const errors = validateExternalServices([
117+
{
118+
name: 'github',
119+
endpoint: 'not-a-url',
120+
},
121+
])
122+
expect(errors).toContain('externalServices[0].endpoint "not-a-url" must be a valid URL')
123+
})
124+
125+
it('should return error if endpoint is empty string', () => {
126+
const errors = validateExternalServices([
127+
{
128+
name: 'github',
129+
endpoint: '',
130+
},
131+
])
132+
expect(errors).toContain('externalServices[0].endpoint "" must be a valid URL')
133+
})
134+
135+
it('should return error if name is not a valid identifier', () => {
136+
const errors = validateExternalServices([
137+
{
138+
name: '123invalid',
139+
endpoint: 'https://api.github.com/graphql',
140+
},
141+
])
142+
expect(errors).toContain('externalServices[0].name "123invalid" must be a valid identifier (letters, numbers, underscore, starting with letter)')
143+
})
144+
145+
it('should return error for names with special characters', () => {
146+
const errors = validateExternalServices([
147+
{
148+
name: 'my-service',
149+
endpoint: 'https://api.example.com/graphql',
150+
},
151+
])
152+
expect(errors).toContain('externalServices[0].name "my-service" must be a valid identifier (letters, numbers, underscore, starting with letter)')
153+
})
154+
155+
it('should allow names with underscores', () => {
156+
const errors = validateExternalServices([
157+
{
158+
name: 'my_service',
159+
endpoint: 'https://api.example.com/graphql',
160+
},
161+
])
162+
expect(errors).toEqual([])
163+
})
164+
165+
it('should allow names starting with uppercase', () => {
166+
const errors = validateExternalServices([
167+
{
168+
name: 'GitHub',
169+
endpoint: 'https://api.github.com/graphql',
170+
},
171+
])
172+
expect(errors).toEqual([])
173+
})
174+
175+
it('should return multiple errors for multiple invalid services', () => {
176+
const errors = validateExternalServices([
177+
{
178+
name: 'valid',
179+
endpoint: 'https://api.example.com/graphql',
180+
},
181+
{
182+
endpoint: 'not-a-url',
183+
},
184+
{
185+
name: '123bad',
186+
endpoint: 'https://api.example.com/graphql',
187+
},
188+
])
189+
expect(errors).toContain('externalServices[1].name is required and must be a string')
190+
expect(errors).toContain('externalServices[1].endpoint "not-a-url" must be a valid URL')
191+
expect(errors).toContain('externalServices[2].name "123bad" must be a valid identifier (letters, numbers, underscore, starting with letter)')
192+
})
193+
194+
it('should accept http URLs', () => {
195+
const errors = validateExternalServices([
196+
{
197+
name: 'local',
198+
endpoint: 'http://localhost:3000/graphql',
199+
},
200+
])
201+
expect(errors).toEqual([])
202+
})
203+
204+
it('should accept URLs with ports', () => {
205+
const errors = validateExternalServices([
206+
{
207+
name: 'local',
208+
endpoint: 'https://localhost:8080/graphql',
209+
},
210+
])
211+
expect(errors).toEqual([])
212+
})
213+
})
214+
})

0 commit comments

Comments
 (0)