Files
supabase/apps/studio/components/interfaces/ConfigDrift/ConfigurationDriftPage.utils.test.ts
Ivan Vasilov e10f7cc808 feat: Add a config drift page in Studio (#48906)
## New Features
- Initial work for showing configuration drift in Studio
- This only works for Github-connected projects and it'll show a banner
if the project state differs from the git-tracked `config.toml`
- Currently behind a feature-flag `ConfigDrift`, enabled on local and
staging.
- There might be drift shown without changing any setting, this is
work-in-progress.

<img width="1217" height="1195" alt="Screenshot 2026-08-19 at 23 12 10"
src="https://github.com/user-attachments/assets/fb0b18d8-1a93-4595-85cc-e8b8a3462847"
/>

## How to test
1. Connect a project to a Github repo
2. Resync the branch on `/dashboard/project/_/branches`. This will
trigger deployment of the `config.toml` on your project
3. Change some settings (I recommend
`dashboard/project/_/auth/providers`
4. A banner should appear on all project pages with a link 


## Tests
- Added coverage for configuration conversion, normalization, matching,
drift detection, and unmanaged settings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-25 13:56:40 +02:00

184 lines
5.5 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
createConfigurationDriftRows,
groupUnmanagedConfigFields,
} from './ConfigurationDriftPage.utils'
import type { GitHubConfigDriftField, UnmanagedConfigField } from './github-config-drift'
const PROJECT_REF = 'abcdefgh'
function driftField(overrides: Partial<GitHubConfigDriftField>): GitHubConfigDriftField {
return {
section: 'api',
configPath: 'api.max_rows',
settingHref: (projectRef) => `/project/${projectRef}/settings/api`,
dashboardValue: 1000,
githubValue: 500,
...overrides,
}
}
function unmanagedField(overrides: Partial<UnmanagedConfigField>): UnmanagedConfigField {
return {
section: 'api',
configPath: 'api.max_rows',
dashboardValue: 1000,
...overrides,
}
}
describe('createConfigurationDriftRows', () => {
it('resolves the setting href against the project ref and marks the row as drifted', () => {
const [row] = createConfigurationDriftRows([driftField({})], PROJECT_REF)
expect(row.status).toBe('drifted')
expect(row.settingHref).toBe('/project/abcdefgh/settings/api')
})
it('uses the hand-written label for a known config path', () => {
const [row] = createConfigurationDriftRows(
[driftField({ configPath: 'auth.enable_signup' })],
PROJECT_REF
)
expect(row.settingLabel).toBe('New user signups')
})
it('labels an external auth provider field as "<provider> · <key>"', () => {
const [row] = createConfigurationDriftRows(
[driftField({ configPath: 'auth.external.google.client_id' })],
PROJECT_REF
)
expect(row.settingLabel).toBe('Google · Client ID')
})
it('falls back to a title-cased last path segment for an unrecognized config path', () => {
const [row] = createConfigurationDriftRows(
[driftField({ configPath: 'some.unknown_setting' })],
PROJECT_REF
)
expect(row.settingLabel).toBe('Unknown Setting')
})
it('builds a scalar diff, formatting booleans and empty values for display', () => {
const [row] = createConfigurationDriftRows(
[
driftField({
configPath: 'auth.enable_signup',
dashboardValue: true,
githubValue: false,
}),
],
PROJECT_REF
)
expect(row.valueDiff).toEqual({
kind: 'scalar',
dashboardValue: 'Enabled',
configValue: 'Disabled',
})
})
it('formats a missing scalar value as "Not set"', () => {
const [row] = createConfigurationDriftRows(
[driftField({ configPath: 'auth.site_url', dashboardValue: '', githubValue: undefined })],
PROJECT_REF
)
expect(row.valueDiff).toEqual({
kind: 'scalar',
dashboardValue: 'Not set',
configValue: 'Not set',
})
})
it('builds a list diff for redirect URLs, normalizing and comparing entries', () => {
const [row] = createConfigurationDriftRows(
[
driftField({
configPath: 'auth.additional_redirect_urls',
dashboardValue: ['https://a.com', 'https://b.com'],
githubValue: [' https://a.com ', 'https://c.com'],
}),
],
PROJECT_REF
)
expect(row.valueDiff).toEqual({
kind: 'list',
onlyInDashboard: ['https://b.com'],
onlyInConfig: ['https://c.com'],
})
})
it('reports no diff entries when redirect URL lists are equal after normalization', () => {
const [row] = createConfigurationDriftRows(
[
driftField({
configPath: 'auth.additional_redirect_urls',
dashboardValue: ['https://a.com'],
githubValue: ['https://a.com', 'https://a.com'],
}),
],
PROJECT_REF
)
expect(row.valueDiff).toEqual({ kind: 'list', onlyInDashboard: [], onlyInConfig: [] })
})
})
describe('groupUnmanagedConfigFields', () => {
it('returns an empty array when there are no unmanaged fields', () => {
expect(groupUnmanagedConfigFields([])).toEqual([])
})
it('groups fields from the same section into a single group', () => {
const groups = groupUnmanagedConfigFields([
unmanagedField({ configPath: 'api.max_rows', dashboardValue: 1000 }),
unmanagedField({ configPath: 'api.enabled', dashboardValue: true }),
])
expect(groups).toHaveLength(1)
expect(groups[0]).toEqual({
section: 'api',
sectionLabel: 'API',
rows: [
{ configPath: 'api.max_rows', label: 'Max rows', value: '1000' },
{ configPath: 'api.enabled', label: 'API enabled', value: 'Enabled' },
],
})
})
it('orders groups by CONFIG_SECTIONS order, not by input order', () => {
const groups = groupUnmanagedConfigFields([
unmanagedField({ section: 'storage', configPath: 'storage.enabled', dashboardValue: true }),
unmanagedField({ section: 'api', configPath: 'api.enabled', dashboardValue: true }),
])
expect(groups.map((group) => group.section)).toEqual(['api', 'storage'])
})
it('formats an empty-value field as "Not set"', () => {
const groups = groupUnmanagedConfigFields([
unmanagedField({ configPath: 'auth.site_url', section: 'auth', dashboardValue: undefined }),
])
expect(groups[0].rows[0].value).toBe('Not set')
})
it('joins a normalized redirect URL list with newlines', () => {
const groups = groupUnmanagedConfigFields([
unmanagedField({
section: 'auth',
configPath: 'auth.additional_redirect_urls',
dashboardValue: ['https://b.com', ' https://a.com ', 'https://a.com'],
}),
])
expect(groups[0].rows[0].value).toBe('https://a.com\nhttps://b.com')
})
})