Document the FreeBSD 15 mac_do rule shape and expose soft setup verification for module/rule state without enforcing live host changes. --- Build: pass | Tests: pass — 2373 passed (704 files)
189 lines
5.6 KiB
TypeScript
189 lines
5.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import type { PlatformRegistry } from '../src/tenant-registry.js';
|
|
|
|
const ENV_KEYS = ['PLATFORM_RUNTIME_HOME', 'TENANT_ID'] as const;
|
|
|
|
afterEach(() => {
|
|
for (const key of ENV_KEYS) delete process.env[key];
|
|
vi.resetModules();
|
|
});
|
|
|
|
function makeRegistry(): PlatformRegistry {
|
|
return {
|
|
platform: {
|
|
id: 'clawdie',
|
|
displayName: 'Clawdie',
|
|
internalDomain: 'ai.home.arpa',
|
|
internalBase: 'home.arpa',
|
|
publicBase: 'example.com',
|
|
controlplaneExposure: 'internal',
|
|
cmsAdminExposure: 'internal',
|
|
codeAdminExposure: 'internal',
|
|
publishingMode: 'internal',
|
|
reservedHostLabels: ['ai', 'cms', 'git', 'web', 'www', 'mail'],
|
|
},
|
|
shared: {
|
|
services: ['cms', 'git', 'web-service'],
|
|
datasets: [],
|
|
jails: ['cms', 'git'],
|
|
},
|
|
tenants: {
|
|
alpha: {
|
|
id: 'alpha',
|
|
displayName: 'Alpha',
|
|
internalDomain: 'alpha.home.arpa',
|
|
service: 'alpha',
|
|
databases: {
|
|
brain: 'alpha_brain',
|
|
ops: 'alpha_ops',
|
|
skills: 'alpha_skills',
|
|
forgejo: 'alpha_forgejo',
|
|
},
|
|
workerJails: ['alpha_ctrl_worker'],
|
|
datasets: ['zroot/alpha-ai'],
|
|
sites: [
|
|
{
|
|
id: 'blog',
|
|
exposure: 'internal',
|
|
fqdn: 'blog.alpha.home.arpa',
|
|
title: 'Blog',
|
|
summary: null,
|
|
},
|
|
{
|
|
id: 'docs',
|
|
exposure: 'internal',
|
|
fqdn: 'docs.alpha.home.arpa',
|
|
title: 'Docs',
|
|
summary: null,
|
|
},
|
|
{
|
|
id: 'draft',
|
|
exposure: 'disabled',
|
|
fqdn: null,
|
|
title: 'Draft',
|
|
summary: null,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('verify platform runtime helpers', () => {
|
|
it('reports mac_do as not applicable off FreeBSD', async () => {
|
|
const { verifyMacDoState } = await import('./verify.js');
|
|
expect(verifyMacDoState('linux')).toEqual({
|
|
module: 'not_applicable',
|
|
rules: 'not_applicable',
|
|
});
|
|
});
|
|
|
|
it('reports loaded mac_do with an empty rule set', async () => {
|
|
const { verifyMacDoState } = await import('./verify.js');
|
|
const execImpl = vi.fn((command: string) => {
|
|
if (command.includes('kldstat')) return Buffer.from('');
|
|
if (command.includes('sysctl')) return ' \n';
|
|
return '';
|
|
}) as unknown as typeof import('child_process').execSync;
|
|
|
|
expect(verifyMacDoState('freebsd', execImpl)).toEqual({
|
|
module: 'loaded',
|
|
rules: 'empty',
|
|
});
|
|
});
|
|
|
|
it('reports configured mac_do rules when sysctl returns a non-empty value', async () => {
|
|
const { verifyMacDoState } = await import('./verify.js');
|
|
const execImpl = vi.fn((command: string) => {
|
|
if (command.includes('kldstat')) return Buffer.from('');
|
|
if (command.includes('sysctl')) return 'uid=1001>uid=1002\n';
|
|
return '';
|
|
}) as unknown as typeof import('child_process').execSync;
|
|
|
|
expect(verifyMacDoState('freebsd', execImpl)).toEqual({
|
|
module: 'loaded',
|
|
rules: 'configured',
|
|
});
|
|
});
|
|
|
|
it('uses the platform service name for PID candidates even with a tenant', async () => {
|
|
process.env.TENANT_ID = 'alpha';
|
|
|
|
const { getVerifyServicePidCandidates } = await import('./verify.js');
|
|
expect(getVerifyServicePidCandidates('/srv/clawdie')).toEqual([
|
|
'/var/run/clawdie.pid',
|
|
'/srv/clawdie/clawdie.pid',
|
|
]);
|
|
});
|
|
|
|
it('checks the platform runtime home for mount allowlist lookup', async () => {
|
|
process.env.PLATFORM_RUNTIME_HOME = '/home/clawdie';
|
|
process.env.TENANT_ID = 'alpha';
|
|
|
|
const { getMountAllowlistHomes } = await import('./verify.js');
|
|
expect(getMountAllowlistHomes('/root')).toEqual([
|
|
'/root',
|
|
'/home/clawdie',
|
|
'/home/alpha',
|
|
]);
|
|
});
|
|
|
|
it('classifies mixed planned and available tenant sites without failing them', async () => {
|
|
const { verifyTenantSitePublishState } = await import('./verify.js');
|
|
|
|
const result = verifyTenantSitePublishState(
|
|
makeRegistry(),
|
|
'/srv/www',
|
|
(targetPath) => targetPath === '/srv/www/sites/alpha/blog/index.html',
|
|
(_webroot, tenantId, siteId) =>
|
|
tenantId === 'alpha' && siteId === 'blog'
|
|
? {
|
|
tenantId: 'alpha',
|
|
siteId: 'blog',
|
|
siteFqdn: 'blog.alpha.home.arpa',
|
|
contentSource: 'generated',
|
|
publishedAt: '2026-04-29T00:00:00.000Z',
|
|
targetDir: '/srv/www/sites/alpha/blog',
|
|
targetIndex: '/srv/www/sites/alpha/blog/index.html',
|
|
sourceDistDir: '/dist/alpha/blog',
|
|
result: 'published',
|
|
}
|
|
: null,
|
|
);
|
|
|
|
expect(result).toEqual({
|
|
state: 'partial',
|
|
declaredSites: 3,
|
|
enabledSites: 2,
|
|
availableSites: 1,
|
|
plannedSites: 1,
|
|
disabledSites: 1,
|
|
manifestMissing: 0,
|
|
staleManifest: 0,
|
|
manifestMismatch: 0,
|
|
});
|
|
});
|
|
|
|
it('flags inconsistent publish state when output exists without a matching manifest', async () => {
|
|
const { verifyTenantSitePublishState } = await import('./verify.js');
|
|
|
|
const result = verifyTenantSitePublishState(
|
|
makeRegistry(),
|
|
'/srv/www',
|
|
(targetPath) => targetPath === '/srv/www/sites/alpha/blog/index.html',
|
|
() => null,
|
|
);
|
|
|
|
expect(result).toEqual({
|
|
state: 'inconsistent',
|
|
declaredSites: 3,
|
|
enabledSites: 2,
|
|
availableSites: 1,
|
|
plannedSites: 1,
|
|
disabledSites: 1,
|
|
manifestMissing: 1,
|
|
staleManifest: 0,
|
|
manifestMismatch: 0,
|
|
});
|
|
});
|
|
});
|