mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 03:36:59 +00:00
e9c262ae6a
* feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails Move global_crash from Host Alerts to new Monitoring > Container Alerts section. Move health gate and env deploy guardrails from Host Alerts to Infrastructure > Stacks > Deploy Guardrails subsection. Host Alerts now contains only host threshold settings (CPU, RAM, disk, alert suppression, and the master host_alerts_enabled toggle). Stacks gains a Deploy Guardrails subsection (node-scoped, admin-gated) alongside the existing Workflow controls (browser-local). Dashboard Crash detection row now routes to Container Alerts. * docs: update crash detection toggle description to match new Container Alerts section
141 lines
6.5 KiB
TypeScript
141 lines
6.5 KiB
TypeScript
/**
|
|
* Structural invariants for the settings registry after the hub reorganization.
|
|
*
|
|
* Guards the ten-group taxonomy and the System Limits split: every item lands in
|
|
* a real group, the three successor sections (Host Alerts, Docker & Storage,
|
|
* Fleet Mesh) exist with the right group/scope/gate, Developer is split from
|
|
* Data Retention, and the renamed labels are applied.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { SETTINGS_GROUPS, SETTINGS_ITEMS, scopeLabel } from '../registry';
|
|
import type { SettingsItemMeta } from '../registry';
|
|
|
|
describe('settings registry', () => {
|
|
it('points every item at a defined group', () => {
|
|
const groupIds = new Set(SETTINGS_GROUPS.map(g => g.id));
|
|
for (const item of SETTINGS_ITEMS) {
|
|
expect(groupIds.has(item.group), `item ${item.id} -> group ${item.group}`).toBe(true);
|
|
}
|
|
});
|
|
|
|
it('gives every group at least one item', () => {
|
|
for (const group of SETTINGS_GROUPS) {
|
|
const count = SETTINGS_ITEMS.filter(i => i.group === group.id).length;
|
|
expect(count, `group ${group.id}`).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('keeps item ids unique', () => {
|
|
const ids = SETTINGS_ITEMS.map(i => i.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
});
|
|
|
|
it('replaces System Limits with three focused sections', () => {
|
|
expect(SETTINGS_ITEMS.some(i => (i.id as string) === 'system')).toBe(false);
|
|
|
|
const hostAlerts = SETTINGS_ITEMS.find(i => i.id === 'host-alerts');
|
|
const dockerStorage = SETTINGS_ITEMS.find(i => i.id === 'docker-storage');
|
|
const fleetMesh = SETTINGS_ITEMS.find(i => i.id === 'fleet-mesh');
|
|
|
|
expect(hostAlerts?.group).toBe('monitoring');
|
|
expect(dockerStorage?.group).toBe('monitoring');
|
|
expect(fleetMesh?.group).toBe('infrastructure');
|
|
|
|
// All three edit the active instance's own settings through the proxy.
|
|
expect(hostAlerts?.scope).toBe('node');
|
|
expect(dockerStorage?.scope).toBe('node');
|
|
expect(fleetMesh?.scope).toBe('node');
|
|
|
|
const containerAlerts = SETTINGS_ITEMS.find(i => i.id === 'container-alerts');
|
|
expect(containerAlerts?.group).toBe('monitoring');
|
|
expect(containerAlerts?.scope).toBe('node');
|
|
expect(containerAlerts?.tier).toBeNull();
|
|
for (const term of ['crash', 'oom', 'healthcheck', 'container']) {
|
|
expect(containerAlerts?.keywords, `keyword ${term}`).toContain(term);
|
|
}
|
|
});
|
|
|
|
it('gates the Fleet Mesh section to admins so the sidebar entry and panel both hide', () => {
|
|
const fleetMesh = SETTINGS_ITEMS.find(i => i.id === 'fleet-mesh');
|
|
expect(fleetMesh?.adminOnly).toBe(true);
|
|
});
|
|
|
|
it('splits Developer into Developer Diagnostics and Data Retention under Operations', () => {
|
|
const developer = SETTINGS_ITEMS.find(i => i.id === 'developer');
|
|
const dataRetention = SETTINGS_ITEMS.find(i => i.id === 'data-retention');
|
|
expect(developer?.label).toBe('Developer Diagnostics');
|
|
expect(developer?.group).toBe('operations');
|
|
expect(dataRetention?.group).toBe('operations');
|
|
});
|
|
|
|
it('registers the Image update checks section under Automation, viewer-visible and node-scoped', () => {
|
|
const item = SETTINGS_ITEMS.find(i => i.id === 'image-updates');
|
|
expect(item?.group).toBe('automation');
|
|
expect(item?.scope).toBe('node');
|
|
expect(item?.tier).toBeNull();
|
|
// No adminOnly: viewers see the section read-only (the control disables
|
|
// itself); the backend PUT is the authoritative admin guard.
|
|
expect(item?.adminOnly).toBeUndefined();
|
|
});
|
|
|
|
it('applies the renamed section labels', () => {
|
|
const byId = new Map(SETTINGS_ITEMS.map(i => [i.id, i]));
|
|
expect(byId.get('notifications')?.label).toBe('Channels');
|
|
expect(byId.get('notification-routing')?.label).toBe('Notification Routing');
|
|
});
|
|
|
|
it('no longer registers the standalone Vulnerability Scanning section (moved to the Security page)', () => {
|
|
expect(SETTINGS_ITEMS.some(i => (i.id as string) === 'security')).toBe(false);
|
|
});
|
|
|
|
it('opens Registries to Community while keeping it admin-only', () => {
|
|
const registries = SETTINGS_ITEMS.find(i => i.id === 'registries');
|
|
expect(registries?.tier).toBeNull();
|
|
expect(registries?.adminOnly).toBe(true);
|
|
});
|
|
|
|
it('registers the Stacks section under Infrastructure with searchable workflow keywords', () => {
|
|
const stacks = SETTINGS_ITEMS.find(i => i.id === 'stacks');
|
|
expect(stacks?.group).toBe('infrastructure');
|
|
expect(stacks?.tier).toBeNull();
|
|
for (const term of ['stack', 'deploy', 'guardrail', 'health gate', 'observation', 'env', 'required variable', 'progress', 'diff', 'save']) {
|
|
expect(stacks?.keywords, `keyword ${term}`).toContain(term);
|
|
}
|
|
});
|
|
|
|
it('scopes the browser-local sections to the browser', () => {
|
|
// Appearance is the only remaining browser-local section. Stacks moved to
|
|
// node scope when Deploy Guardrails (backend settings) were added to it
|
|
// alongside the existing browser-local Workflow controls.
|
|
expect(SETTINGS_ITEMS.find(i => i.id === 'appearance')?.scope).toBe('browser');
|
|
expect(SETTINGS_ITEMS.find(i => i.id === 'stacks')?.scope).toBe('node');
|
|
});
|
|
|
|
it('exposes the Calm/Signature appearance keywords for search', () => {
|
|
const appearance = SETTINGS_ITEMS.find(i => i.id === 'appearance');
|
|
for (const term of ['calm', 'signature', 'readability', 'heading', 'chart', 'motion', 'effects']) {
|
|
expect(appearance?.keywords, `keyword ${term}`).toContain(term);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('scopeLabel', () => {
|
|
const item = (over: Partial<SettingsItemMeta>): SettingsItemMeta => ({
|
|
id: 'stacks', group: 'infrastructure', label: 'X', description: '',
|
|
keywords: [], tier: null, scope: 'global', ...over,
|
|
});
|
|
|
|
it('reads browser for browser-scoped sections regardless of their group', () => {
|
|
expect(scopeLabel(item({ scope: 'browser', group: 'personal' }))).toBe('browser');
|
|
expect(scopeLabel(item({ scope: 'browser', group: 'infrastructure' }))).toBe('browser');
|
|
});
|
|
|
|
it('reads operator for the signed-in Account (personal group)', () => {
|
|
expect(scopeLabel(item({ scope: 'global', group: 'personal' }))).toBe('operator');
|
|
});
|
|
|
|
it('reads global for other non-node, non-browser groups', () => {
|
|
expect(scopeLabel(item({ scope: 'global', group: 'access' }))).toBe('global');
|
|
});
|
|
});
|