mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 14:18:02 +00:00
feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)
* feat: block self-stack lifecycle ops with UI and preflight guardrails Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project. Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions. Add SelfStackProtectedDialog and self-managed-stack preflight warning. Closes #1564 * fix: add missing stackSelfFlags mock to useSidebarContextMenu test The production hook now reads stackListState.stackSelfFlags[file], but the test mock did not include it, causing 6 tests to fail with TypeError: Cannot read properties of undefined (reading 'web.yml'). * fix: harden self-stack protection during startup Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR. Align status decoration and route guards on Docker label fallback detection. Block rollback and service-level stop on the protected self stack. * fix: add self_stack_location to diagnostics-route expected check IDs
This commit is contained in:
@@ -21,6 +21,7 @@ function baseProbes(overrides: Partial<EnvironmentProbes> = {}): EnvironmentProb
|
||||
composeVersion: async () => 'v2.29.0',
|
||||
accessDir: async () => ({ exists: true, isDir: true, writable: true }),
|
||||
bindMounts: async () => [{ source: '/app/compose', destination: '/app/compose' }],
|
||||
selfStackDirectoryName: async () => null,
|
||||
diskUsage: async () => ({ usePercent: 40, freeBytes: 50 * 1024 ** 3 }),
|
||||
...overrides,
|
||||
};
|
||||
@@ -42,7 +43,7 @@ describe('collectEnvironmentReport', () => {
|
||||
it('passes every check on a healthy environment', async () => {
|
||||
const { checks } = await collectEnvironmentReport(baseProbes());
|
||||
expect(checks.map(c => c.id)).toEqual([
|
||||
'docker_socket', 'docker_compose', 'compose_dir', 'path_mapping', 'tls', 'disk_space',
|
||||
'docker_socket', 'docker_compose', 'compose_dir', 'self_stack_location', 'path_mapping', 'tls', 'disk_space',
|
||||
]);
|
||||
expect(checks.every(c => c.status === 'pass')).toBe(true);
|
||||
});
|
||||
@@ -61,6 +62,45 @@ describe('collectEnvironmentReport', () => {
|
||||
}
|
||||
});
|
||||
|
||||
describe('self_stack_location', () => {
|
||||
it('warns when Sencho compose project is inside COMPOSE_DIR', async () => {
|
||||
const { checks } = await collectEnvironmentReport(baseProbes({
|
||||
selfStackDirectoryName: async () => 'sencho',
|
||||
accessDir: async (dir) => ({
|
||||
exists: dir.replace(/\\/g, '/').endsWith('/app/compose') || dir.replace(/\\/g, '/').endsWith('/app/compose/sencho'),
|
||||
isDir: true,
|
||||
writable: true,
|
||||
}),
|
||||
}));
|
||||
const c = byId(checks, 'self_stack_location');
|
||||
expect(c.status).toBe('warn');
|
||||
expect(c.detail).toMatch(/inside COMPOSE_DIR/i);
|
||||
expect(remediationOf(c)).toMatch(/Fleet -> Node Update/i);
|
||||
});
|
||||
|
||||
it('passes when the running project is not a managed stack directory', async () => {
|
||||
const { checks } = await collectEnvironmentReport(baseProbes({
|
||||
selfStackDirectoryName: async () => 'sencho',
|
||||
accessDir: async (dir) => ({
|
||||
exists: dir.replace(/\\/g, '/').endsWith('/app/compose'),
|
||||
isDir: true,
|
||||
writable: true,
|
||||
}),
|
||||
}));
|
||||
const c = byId(checks, 'self_stack_location');
|
||||
expect(c.status).toBe('pass');
|
||||
});
|
||||
|
||||
it('warns when self-stack location cannot be verified', async () => {
|
||||
const { checks } = await collectEnvironmentReport(baseProbes({
|
||||
selfStackDirectoryName: async () => { throw new Error('inspect failed'); },
|
||||
}));
|
||||
const c = byId(checks, 'self_stack_location');
|
||||
expect(c.status).toBe('warn');
|
||||
expect(c.detail).toMatch(/Could not verify/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('docker_socket', () => {
|
||||
it('flags a permission error distinctly', async () => {
|
||||
const { checks } = await collectEnvironmentReport(baseProbes({
|
||||
|
||||
Reference in New Issue
Block a user