Skip to content

Commit 42451a6

Browse files
committed
test: Add comprehensive tests for health route including registration, response validation, and error handling
1 parent e5aeb67 commit 42451a6

File tree

3 files changed

+484
-0
lines changed

3 files changed

+484
-0
lines changed

services/backend/tests/unit/global-settings/index.test.ts

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ vi.mock('../../../src/db', () => ({
2525
getSchema: vi.fn(),
2626
}))
2727

28+
// Mock the encryption module
29+
vi.mock('../../../src/utils/encryption', () => ({
30+
encrypt: vi.fn((value) => `encrypted_${value}`),
31+
}))
32+
33+
// Mock path module
34+
vi.mock('path', () => ({
35+
default: {
36+
join: vi.fn((...args) => args.join('/')),
37+
}
38+
}))
39+
2840
describe('GlobalSettingsInitService', () => {
2941
const mockGlobalSettingsService = GlobalSettingsService as any
3042

@@ -130,6 +142,290 @@ describe('GlobalSettingsInitService', () => {
130142
// Should throw the file system error
131143
await expect(GlobalSettingsInitService.loadSettingsDefinitions()).rejects.toThrow('File system error')
132144
})
145+
146+
it('should load settings modules from files', async () => {
147+
const fs = await import('fs')
148+
const mockFs = fs.default as any
149+
150+
// Mock file system to return test files
151+
mockFs.readdirSync.mockReturnValue(['smtp.ts', 'global.ts', 'index.ts', 'types.ts', 'helpers.ts'])
152+
153+
// Mock dynamic imports
154+
const mockSmtpModule = {
155+
smtpSettings: {
156+
group: { id: 'smtp', name: 'SMTP Settings', sort_order: 1 },
157+
settings: [
158+
{ key: 'smtp.host', defaultValue: '', type: 'string', description: 'SMTP host', encrypted: false, required: true }
159+
]
160+
}
161+
}
162+
163+
const mockGlobalModule = {
164+
globalSettings: {
165+
group: { id: 'global', name: 'Global Settings', sort_order: 0 },
166+
settings: [
167+
{ key: 'global.page_url', defaultValue: 'http://localhost:5173', type: 'string', description: 'Page URL', encrypted: false, required: false }
168+
]
169+
}
170+
}
171+
172+
// Mock the dynamic import function
173+
const originalImport = global.__dirname
174+
vi.stubGlobal('__dirname', '/test/path')
175+
176+
// Mock import calls
177+
vi.doMock('/test/path/smtp.ts', () => mockSmtpModule)
178+
vi.doMock('/test/path/global.ts', () => mockGlobalModule)
179+
180+
await GlobalSettingsInitService.loadSettingsDefinitions()
181+
182+
expect(GlobalSettingsInitService['isLoaded']).toBe(true)
183+
expect(GlobalSettingsInitService['settingsModules']).toHaveLength(2)
184+
})
185+
186+
it('should handle import errors gracefully', async () => {
187+
const fs = await import('fs')
188+
const mockFs = fs.default as any
189+
190+
mockFs.readdirSync.mockReturnValue(['invalid.ts'])
191+
vi.stubGlobal('__dirname', '/test/path')
192+
193+
// This should not throw, but continue processing
194+
await expect(GlobalSettingsInitService.loadSettingsDefinitions()).resolves.not.toThrow()
195+
expect(GlobalSettingsInitService['isLoaded']).toBe(true)
196+
})
197+
})
198+
199+
describe('initializeSettings', () => {
200+
it('should initialize settings successfully', async () => {
201+
// Setup test modules
202+
GlobalSettingsInitService['settingsModules'] = [
203+
{
204+
group: { id: 'test', name: 'Test Group', sort_order: 0 },
205+
settings: [
206+
{ key: 'test.setting1', defaultValue: 'value1', type: 'string', description: 'Test setting', encrypted: false, required: false }
207+
]
208+
}
209+
]
210+
GlobalSettingsInitService['isLoaded'] = true
211+
212+
mockGlobalSettingsService.exists.mockResolvedValue(false)
213+
214+
const result = await GlobalSettingsInitService.initializeSettings()
215+
216+
expect(result.totalModules).toBe(1)
217+
expect(result.totalSettings).toBe(1)
218+
expect(result.created).toBeGreaterThanOrEqual(0)
219+
expect(result.skipped).toBeGreaterThanOrEqual(0)
220+
})
221+
222+
it('should skip existing settings', async () => {
223+
GlobalSettingsInitService['settingsModules'] = [
224+
{
225+
group: { id: 'test', name: 'Test Group', sort_order: 0 },
226+
settings: [
227+
{ key: 'test.setting1', defaultValue: 'value1', type: 'string', description: 'Test setting', encrypted: false, required: false }
228+
]
229+
}
230+
]
231+
GlobalSettingsInitService['isLoaded'] = true
232+
233+
mockGlobalSettingsService.exists.mockResolvedValue(true)
234+
235+
const result = await GlobalSettingsInitService.initializeSettings()
236+
237+
expect(result.totalModules).toBe(1)
238+
expect(result.totalSettings).toBe(1)
239+
expect(result.skipped).toBeGreaterThanOrEqual(0)
240+
})
241+
242+
it('should load settings definitions if not loaded', async () => {
243+
GlobalSettingsInitService['isLoaded'] = false
244+
245+
const fs = await import('fs')
246+
const mockFs = fs.default as any
247+
mockFs.readdirSync.mockReturnValue([])
248+
249+
const result = await GlobalSettingsInitService.initializeSettings()
250+
251+
expect(GlobalSettingsInitService['isLoaded']).toBe(true)
252+
expect(result.totalModules).toBe(0)
253+
})
254+
})
255+
256+
describe('validateRequiredSettings', () => {
257+
beforeEach(() => {
258+
GlobalSettingsInitService['settingsModules'] = [
259+
{
260+
group: { id: 'smtp', name: 'SMTP Settings', sort_order: 1 },
261+
settings: [
262+
{ key: 'smtp.host', defaultValue: '', type: 'string', description: 'SMTP host', encrypted: false, required: true },
263+
{ key: 'smtp.port', defaultValue: 587, type: 'number', description: 'SMTP port', encrypted: false, required: true },
264+
{ key: 'smtp.from_name', defaultValue: 'DeployStack', type: 'string', description: 'From name', encrypted: false, required: false }
265+
]
266+
},
267+
{
268+
group: { id: 'global', name: 'Global Settings', sort_order: 0 },
269+
settings: [
270+
{ key: 'global.page_url', defaultValue: 'http://localhost:5173', type: 'string', description: 'Page URL', encrypted: false, required: true }
271+
]
272+
}
273+
]
274+
GlobalSettingsInitService['isLoaded'] = true
275+
})
276+
277+
it('should return valid when all required settings have values', async () => {
278+
mockGlobalSettingsService.get
279+
.mockResolvedValueOnce({ key: 'smtp.host', value: 'smtp.example.com', type: 'string' })
280+
.mockResolvedValueOnce({ key: 'smtp.port', value: '587', type: 'number' })
281+
.mockResolvedValueOnce({ key: 'global.page_url', value: 'https://example.com', type: 'string' })
282+
283+
const result = await GlobalSettingsInitService.validateRequiredSettings()
284+
285+
expect(result.valid).toBe(true)
286+
expect(result.missing).toEqual([])
287+
expect(result.groups.smtp.missing).toBe(0)
288+
expect(result.groups.global.missing).toBe(0)
289+
})
290+
291+
it('should return invalid when required settings are missing', async () => {
292+
mockGlobalSettingsService.get
293+
.mockResolvedValueOnce(null) // smtp.host missing
294+
.mockResolvedValueOnce({ key: 'smtp.port', value: '587', type: 'number' })
295+
.mockResolvedValueOnce({ key: 'global.page_url', value: '', type: 'string' }) // empty value
296+
297+
const result = await GlobalSettingsInitService.validateRequiredSettings()
298+
299+
expect(result.valid).toBe(false)
300+
expect(result.missing).toEqual(['smtp.host', 'global.page_url'])
301+
expect(result.groups.smtp.missing).toBe(1)
302+
expect(result.groups.smtp.missingKeys).toEqual(['smtp.host'])
303+
expect(result.groups.global.missing).toBe(1)
304+
expect(result.groups.global.missingKeys).toEqual(['global.page_url'])
305+
})
306+
307+
it('should handle database errors gracefully', async () => {
308+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
309+
310+
const result = await GlobalSettingsInitService.validateRequiredSettings()
311+
312+
expect(result.valid).toBe(false)
313+
expect(result.missing).toEqual(['smtp.host', 'smtp.port', 'global.page_url'])
314+
})
315+
316+
it('should load settings definitions if not loaded', async () => {
317+
// Reset state completely for this test
318+
GlobalSettingsInitService['isLoaded'] = false
319+
GlobalSettingsInitService['settingsModules'] = []
320+
321+
const fs = await import('fs')
322+
const mockFs = fs.default as any
323+
mockFs.readdirSync.mockReturnValue([])
324+
325+
const result = await GlobalSettingsInitService.validateRequiredSettings()
326+
327+
expect(GlobalSettingsInitService['isLoaded']).toBe(true)
328+
expect(result.missing).toEqual([]) // No required settings when no modules loaded
329+
expect(Object.keys(result.groups)).toEqual([]) // No groups when no modules loaded
330+
})
331+
})
332+
333+
describe('helper methods', () => {
334+
describe('isGitHubOAuthConfigured', () => {
335+
it('should return true when GitHub OAuth is configured and enabled', async () => {
336+
mockGlobalSettingsService.get
337+
.mockResolvedValueOnce({ key: 'github.oauth.client_id', value: 'client123', type: 'string' })
338+
.mockResolvedValueOnce({ key: 'github.oauth.client_secret', value: 'secret456', type: 'string' })
339+
.mockResolvedValueOnce({ key: 'github.oauth.enabled', value: 'true', type: 'boolean' })
340+
.mockResolvedValueOnce({ key: 'github.oauth.callback_url', value: 'http://localhost:3000/callback', type: 'string' })
341+
.mockResolvedValueOnce({ key: 'github.oauth.scope', value: 'user:email', type: 'string' })
342+
343+
const result = await GlobalSettingsInitService.isGitHubOAuthConfigured()
344+
expect(result).toBe(true)
345+
})
346+
347+
it('should return false when GitHub OAuth is not configured', async () => {
348+
mockGlobalSettingsService.get.mockResolvedValue(null)
349+
350+
const result = await GlobalSettingsInitService.isGitHubOAuthConfigured()
351+
expect(result).toBe(false)
352+
})
353+
})
354+
355+
describe('isEmailRegistrationEnabled', () => {
356+
it('should return true when email registration is enabled', async () => {
357+
mockGlobalSettingsService.get.mockResolvedValue({
358+
key: 'global.enable_email_registration',
359+
value: 'true',
360+
type: 'boolean'
361+
})
362+
363+
const result = await GlobalSettingsInitService.isEmailRegistrationEnabled()
364+
expect(result).toBe(true)
365+
})
366+
367+
it('should return false when email registration is disabled', async () => {
368+
mockGlobalSettingsService.get.mockResolvedValue({
369+
key: 'global.enable_email_registration',
370+
value: 'false',
371+
type: 'boolean'
372+
})
373+
374+
const result = await GlobalSettingsInitService.isEmailRegistrationEnabled()
375+
expect(result).toBe(false)
376+
})
377+
378+
it('should return false when setting does not exist', async () => {
379+
mockGlobalSettingsService.get.mockResolvedValue(null)
380+
381+
const result = await GlobalSettingsInitService.isEmailRegistrationEnabled()
382+
expect(result).toBe(false) // null?.value === 'true' is false
383+
})
384+
})
385+
})
386+
387+
describe('error handling in configuration getters', () => {
388+
it('should handle errors in getSmtpConfiguration', async () => {
389+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
390+
391+
const config = await GlobalSettingsInitService.getSmtpConfiguration()
392+
expect(config).toBeNull()
393+
})
394+
395+
it('should handle errors in getGitHubOAuthConfiguration', async () => {
396+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
397+
398+
const config = await GlobalSettingsInitService.getGitHubOAuthConfiguration()
399+
expect(config).toBeNull()
400+
})
401+
402+
it('should handle errors in getGlobalConfiguration', async () => {
403+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
404+
405+
const config = await GlobalSettingsInitService.getGlobalConfiguration()
406+
expect(config).toBeNull()
407+
})
408+
409+
it('should handle errors in isEmailSendingEnabled', async () => {
410+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
411+
412+
const result = await GlobalSettingsInitService.isEmailSendingEnabled()
413+
expect(result).toBe(false)
414+
})
415+
416+
it('should handle errors in isLoginEnabled', async () => {
417+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
418+
419+
const result = await GlobalSettingsInitService.isLoginEnabled()
420+
expect(result).toBe(true) // Default to enabled on error
421+
})
422+
423+
it('should handle errors in getPageUrl', async () => {
424+
mockGlobalSettingsService.get.mockRejectedValue(new Error('Database error'))
425+
426+
const result = await GlobalSettingsInitService.getPageUrl()
427+
expect(result).toBe('http://localhost:5173') // Default fallback
428+
})
133429
})
134430

135431
describe('configuration getters', () => {

0 commit comments

Comments
 (0)