Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/lib/SettingsManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2574,15 +2574,25 @@ const error: { code?: string; message: string } = {
expect(result).toEqual({})
})

it('should warn but return empty object on validation error', async () => {
const invalidSettings = {
mainBranch: 123, // Invalid: should be string
}
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(invalidSettings))
it('should warn but return empty object when content is not a JSON object', async () => {
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(['not', 'an', 'object']))

const result = await settingsManager['loadGlobalSettingsFile']()
expect(result).toEqual({})
})

it('should pass schema-invalid global settings through for post-merge validation', async () => {
// Schema validation no longer runs per-file — it happens after merge in
// loadSettings() so partial configs (e.g. shared apiToken in global with
// teamId set per-project) are not rejected before the merge completes.
const partiallyInvalidSettings = {
mainBranch: 123, // Bad type — caught later by loadSettings()
}
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify(partiallyInvalidSettings))

const result = await settingsManager['loadGlobalSettingsFile']()
expect(result).toEqual(partiallyInvalidSettings)
})
})

describe('loadSettings with global settings', () => {
Expand Down
21 changes: 10 additions & 11 deletions src/lib/SettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1369,19 +1369,18 @@ export class SettingsManager {
return {}
}

// Validate with non-defaulting schema
try {
const validated = IloomSettingsSchemaNoDefaults.strict().parse(parsed)
return validated
} catch (error) {
if (error instanceof z.ZodError) {
const errorMsg = this.formatAllZodErrors(error, 'global settings')
logger.warn(`${errorMsg.message}. Ignoring global settings.`)
} else {
logger.warn(`Validation error in global settings: ${error instanceof Error ? error.message : 'Unknown error'}. Ignoring global settings.`)
}
// Only enforce that the file is a JSON object here. Full schema validation
// happens post-merge in loadSettings() so that partial configs (e.g. a
// shared apiToken in global with teamId set per-project) are not rejected
// before the merge has a chance to complete them.
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
logger.warn(
`Global settings at ${settingsPath} is not a JSON object (received ${Array.isArray(parsed) ? 'array' : typeof parsed}). Ignoring global settings.`,
)
return {}
}

return parsed as z.infer<typeof IloomSettingsSchemaNoDefaults>
} catch (error) {
// File not found is not an error - return empty settings
if ((error as { code?: string }).code === 'ENOENT') {
Expand Down
Loading