import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb'; import type { MeshActivityEvent, MeshDataPlaneStatus } from '../services/MeshService'; let tmpDir: string; let MeshService: typeof import('../services/MeshService').MeshService; let DockerController: typeof import('../services/DockerController').default; beforeAll(async () => { tmpDir = await setupTestDb(); ({ MeshService } = await import('../services/MeshService')); ({ default: DockerController } = await import('../services/DockerController')); }); afterAll(() => { cleanupTestDb(tmpDir); }); type MutableSvc = { activity: MeshActivityEvent[]; senchoIp: string | null; meshSubnet: string; networkSetupError: string | null; dataPlaneStatus: MeshDataPlaneStatus; }; let prevSubnetEnv: string | undefined; let prevHostnameEnv: string | undefined; beforeEach(() => { prevSubnetEnv = process.env.SENCHO_MESH_SUBNET; prevHostnameEnv = process.env.HOSTNAME; const svc = MeshService.getInstance() as unknown as MutableSvc; svc.activity = []; svc.senchoIp = null; svc.meshSubnet = '172.30.0.0/24'; svc.networkSetupError = null; svc.dataPlaneStatus = { ok: false, reason: 'not_started', message: 'mesh data plane has not initialized yet', subnet: '', }; }); afterEach(() => { if (prevSubnetEnv === undefined) delete process.env.SENCHO_MESH_SUBNET; else process.env.SENCHO_MESH_SUBNET = prevSubnetEnv; if (prevHostnameEnv === undefined) delete process.env.HOSTNAME; else process.env.HOSTNAME = prevHostnameEnv; vi.restoreAllMocks(); }); function callSetup(svc: import('../services/MeshService').MeshService): Promise { return (svc as unknown as { setupMeshNetwork: () => Promise }).setupMeshNetwork(); } function lastDisable(svc: import('../services/MeshService').MeshService): MeshActivityEvent | undefined { const all = (svc as unknown as MutableSvc).activity; return all.filter((e) => e.type === 'mesh.disable').slice(-1)[0]; } type FakeController = { createNetwork: ReturnType; inspectNetwork: ReturnType; connectContainerToNetwork: ReturnType; }; function mockDocker(overrides: Partial = {}): FakeController { const fake: FakeController = { createNetwork: vi.fn().mockResolvedValue(undefined), inspectNetwork: vi.fn(), connectContainerToNetwork: vi.fn().mockResolvedValue(undefined), ...overrides, }; vi.spyOn(DockerController, 'getInstance').mockReturnValue( fake as unknown as ReturnType, ); return fake; } describe('MeshService.setupMeshNetwork failure classification', () => { it('classifies an invalid CIDR as subnet_invalid', async () => { process.env.SENCHO_MESH_SUBNET = 'not-a-cidr'; process.env.HOSTNAME = 'sencho'; mockDocker(); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('subnet_invalid'); expect(status.subnet).toBe('not-a-cidr'); const entry = lastDisable(svc); expect(entry?.level).toBe('error'); expect(entry?.details?.reason).toBe('subnet_invalid'); }); it('classifies a Docker pool-overlap error as subnet_overlap', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker({ createNetwork: vi.fn().mockRejectedValue( Object.assign(new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }), ), }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.reason).toBe('subnet_overlap'); expect(status.subnet).toBe('10.42.0.0/24'); expect(status.message).toMatch(/overlap/i); const entry = lastDisable(svc); expect(entry?.level).toBe('error'); expect(entry?.details?.reason).toBe('subnet_overlap'); }); it('classifies an existing-network mismatch as subnet_mismatch', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker({ createNetwork: vi.fn().mockRejectedValue({ statusCode: 409, message: 'network already exists' }), inspectNetwork: vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: '172.30.0.0/24' }] } }), }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.reason).toBe('subnet_mismatch'); expect(status.message).toMatch(/exists with subnet/i); }); it('classifies an address-already-in-use attach error as ip_in_use', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker({ connectContainerToNetwork: vi.fn().mockRejectedValue(new Error('Address already in use')), }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getDataPlaneStatus().reason).toBe('ip_in_use'); expect(lastDisable(svc)?.level).toBe('error'); }); it('classifies a generic attach failure as attach_failed', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker({ connectContainerToNetwork: vi.fn().mockRejectedValue(new Error('Docker daemon explosion')), }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getDataPlaneStatus().reason).toBe('attach_failed'); }); it('records the HOSTNAME-unset path as not_in_docker at level warn', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; delete process.env.HOSTNAME; mockDocker(); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('not_in_docker'); const entry = lastDisable(svc); expect(entry?.level).toBe('warn'); expect(entry?.details?.reason).toBe('not_in_docker'); }); it('records the 404-on-inspect path as not_in_docker at level warn', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'laptop-hostname'; mockDocker({ connectContainerToNetwork: vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such container' }), }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.reason).toBe('not_in_docker'); expect(lastDisable(svc)?.level).toBe('warn'); }); it('records success as ok', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker(); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.reason).toBe('ok'); expect(status.subnet).toBe('10.42.0.0/24'); expect(status.message).toBeNull(); }); it('preserves the legacy networkSetupError getter on failure', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; mockDocker({ createNetwork: vi.fn().mockRejectedValue( Object.assign(new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }), ), }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getNetworkSetupError()).toMatch(/overlap/i); }); }); describe('MeshService.setupMeshNetwork subnet auto-fallback', () => { it('iterates past the first overlapping candidate when SENCHO_MESH_SUBNET is unset', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const overlap = Object.assign( new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }, ); const createNetwork = vi.fn() .mockRejectedValueOnce(overlap) .mockResolvedValueOnce(undefined); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.subnet).toBe('172.31.0.0/24'); expect(createNetwork).toHaveBeenCalledTimes(2); }); it('records subnet_overlap with every tried candidate when all candidates overlap', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const overlap = Object.assign( new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }, ); const createNetwork = vi.fn().mockRejectedValue(overlap); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('subnet_overlap'); expect(createNetwork).toHaveBeenCalledTimes(4); expect(status.message).toContain('172.30.0.0/24'); expect(status.message).toContain('172.31.0.0/24'); expect(status.message).toContain('10.42.0.0/24'); expect(status.message).toContain('10.43.0.0/24'); expect(status.message).toMatch(/SENCHO_MESH_SUBNET/); }); it('adopts an existing sencho_mesh subnet when SENCHO_MESH_SUBNET is unset', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const createNetwork = vi.fn(); const inspectNetwork = vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: '192.168.42.0/24' }] }, }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.subnet).toBe('192.168.42.0/24'); expect(createNetwork).not.toHaveBeenCalled(); }); it('classifies a non-404 inspectNetwork failure as attach_failed without trying to create', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const createNetwork = vi.fn(); const inspectNetwork = vi.fn().mockRejectedValue( Object.assign(new Error('daemon unresponsive'), { statusCode: 500 }), ); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('attach_failed'); expect(createNetwork).not.toHaveBeenCalled(); }); it('skips create when SENCHO_MESH_SUBNET matches the existing sencho_mesh subnet', async () => { process.env.SENCHO_MESH_SUBNET = '172.30.0.0/24'; process.env.HOSTNAME = 'sencho'; const createNetwork = vi.fn(); const inspectNetwork = vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: '172.30.0.0/24' }] }, }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.subnet).toBe('172.30.0.0/24'); expect(createNetwork).not.toHaveBeenCalled(); }); it('keeps the operator-explicit path strict (no candidate fallback)', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const overlap = Object.assign( new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }, ); const createNetwork = vi.fn().mockRejectedValue(overlap); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('subnet_overlap'); expect(status.subnet).toBe('10.42.0.0/24'); expect(createNetwork).toHaveBeenCalledTimes(1); }); it('bails the candidate loop on a non-overlap createNetwork failure', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const overlap = Object.assign( new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }, ); const daemonErr = Object.assign( new Error('daemon attach error: out of inodes'), { statusCode: 500 }, ); // First candidate overlaps (recoverable, advance), second hits a // generic daemon error (unrecoverable, bail). const createNetwork = vi.fn() .mockRejectedValueOnce(overlap) .mockRejectedValueOnce(daemonErr); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('attach_failed'); // Stops at the second candidate; does not advance to the third. expect(createNetwork).toHaveBeenCalledTimes(2); expect(status.subnet).toBe('172.31.0.0/24'); }); it('classifies an unparseable existing subnet as subnet_invalid on the adopt path', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const createNetwork = vi.fn(); const inspectNetwork = vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: 'not-a-cidr' }] }, }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('subnet_invalid'); expect(status.subnet).toBe('not-a-cidr'); expect(createNetwork).not.toHaveBeenCalled(); }); it('classifies a generic explicit-env createNetwork failure as attach_failed', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const daemonErr = Object.assign( new Error('daemon attach error: out of inodes'), { statusCode: 500 }, ); const createNetwork = vi.fn().mockRejectedValue(daemonErr); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('attach_failed'); expect(status.subnet).toBe('10.42.0.0/24'); }); it('treats a TOCTOU 409 in the explicit-env path as idempotent when the race-winner subnet matches', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const conflict = Object.assign( new Error('network with name sencho_mesh already exists'), { statusCode: 409 }, ); const createNetwork = vi.fn().mockRejectedValue(conflict); // First inspect (pre-create probe): 404. Second inspect (post-409 // race-winner check): the matching subnet. const inspectNetwork = vi.fn() .mockRejectedValueOnce({ statusCode: 404, message: 'no such network' }) .mockResolvedValueOnce({ IPAM: { Config: [{ Subnet: '10.42.0.0/24' }] } }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.subnet).toBe('10.42.0.0/24'); }); it('treats a TOCTOU 409 in the explicit-env path as subnet_mismatch when the race-winner differs', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const conflict = Object.assign( new Error('network with name sencho_mesh already exists'), { statusCode: 409 }, ); const createNetwork = vi.fn().mockRejectedValue(conflict); const inspectNetwork = vi.fn() .mockRejectedValueOnce({ statusCode: 404, message: 'no such network' }) .mockResolvedValueOnce({ IPAM: { Config: [{ Subnet: '172.30.0.0/24' }] } }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(false); expect(status.reason).toBe('subnet_mismatch'); }); }); describe('MeshService static IP reservation (F-13)', () => { it('emits IPRange for the upper half of the subnet when creating with an explicit env subnet', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const createNetwork = vi.fn().mockResolvedValue(undefined); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getDataPlaneStatus().ok).toBe(true); expect(createNetwork).toHaveBeenCalledTimes(1); const payload = createNetwork.mock.calls[0][0] as { IPAM?: { Config?: Array<{ Subnet?: string; IPRange?: string }> }; }; expect(payload.IPAM?.Config?.[0]?.Subnet).toBe('10.42.0.0/24'); expect(payload.IPAM?.Config?.[0]?.IPRange).toBe('10.42.0.128/25'); }); it('emits the IPRange on the winning candidate when env is unset and the first candidate overlaps', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const overlap = Object.assign( new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }, ); const createNetwork = vi.fn() .mockRejectedValueOnce(overlap) .mockResolvedValueOnce(undefined); const inspectNetwork = vi.fn().mockRejectedValue({ statusCode: 404, message: 'no such network' }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getDataPlaneStatus().ok).toBe(true); expect(svc.getDataPlaneStatus().subnet).toBe('172.31.0.0/24'); expect(createNetwork).toHaveBeenCalledTimes(2); const winningPayload = createNetwork.mock.calls[1][0] as { IPAM?: { Config?: Array<{ Subnet?: string; IPRange?: string }> }; }; expect(winningPayload.IPAM?.Config?.[0]?.Subnet).toBe('172.31.0.0/24'); expect(winningPayload.IPAM?.Config?.[0]?.IPRange).toBe('172.31.0.128/25'); }); it('emits a legacy warn when adopting a sencho_mesh without the upper-half IPRange', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const createNetwork = vi.fn(); // Legacy network: Subnet only, no IPRange (Docker defaults auto- // allocation to the entire subnet, including .2). const inspectNetwork = vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: '172.30.0.0/24' }] }, }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); expect(status.subnet).toBe('172.30.0.0/24'); expect(createNetwork).not.toHaveBeenCalled(); const all = (svc as unknown as { activity: MeshActivityEvent[] }).activity; const warns = all.filter((e) => e.level === 'warn' && /without the upper-half IPAM IPRange/.test(e.message)); expect(warns).toHaveLength(1); expect(warns[0].details).toMatchObject({ subnet: '172.30.0.0/24', expectedIpRange: '172.30.0.128/25', actualIpRange: null, }); const meshLines = warnSpy.mock.calls .map((args) => String(args[0])) .filter((line) => /\[Mesh\] sencho_mesh adopted without the upper-half IPAM IPRange/.test(line)); expect(meshLines.length).toBeGreaterThan(0); }); it('stays silent on the adopt path when the existing sencho_mesh already carries the upper-half IPRange', async () => { delete process.env.SENCHO_MESH_SUBNET; process.env.HOSTNAME = 'sencho'; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const createNetwork = vi.fn(); const inspectNetwork = vi.fn().mockResolvedValue({ IPAM: { Config: [{ Subnet: '172.30.0.0/24', IPRange: '172.30.0.128/25', }], }, }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); const status = svc.getDataPlaneStatus(); expect(status.ok).toBe(true); const all = (svc as unknown as { activity: MeshActivityEvent[] }).activity; const warns = all.filter((e) => e.level === 'warn' && /IPAM IPRange/.test(e.message)); expect(warns).toHaveLength(0); const meshLines = warnSpy.mock.calls .map((args) => String(args[0])) .filter((line) => /IPAM IPRange/.test(line)); expect(meshLines).toHaveLength(0); }); it('emits the legacy warn when a TOCTOU 409 race-winner is a legacy network', async () => { // Operator-explicit subnet matches a race-winner that pre-dates the // upper-half IPRange bias: another process created sencho_mesh // between our initial inspect and our create. We adopt it, the data // plane comes up, and the warn fires because the race-winner's IPAM // block has no IPRange entry. Exercises the subtle assignment in // setupMeshNetwork that reassigns existingSubnet = raceWinner. process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const conflict = Object.assign( new Error('network with name sencho_mesh already exists'), { statusCode: 409 }, ); const createNetwork = vi.fn().mockRejectedValue(conflict); const inspectNetwork = vi.fn() .mockRejectedValueOnce({ statusCode: 404, message: 'no such network' }) .mockResolvedValueOnce({ IPAM: { Config: [{ Subnet: '10.42.0.0/24' }] } }); mockDocker({ createNetwork, inspectNetwork }); const svc = MeshService.getInstance(); await callSetup(svc); expect(svc.getDataPlaneStatus().ok).toBe(true); const all = (svc as unknown as { activity: MeshActivityEvent[] }).activity; const warns = all.filter((e) => e.level === 'warn' && /without the upper-half IPAM IPRange/.test(e.message)); expect(warns).toHaveLength(1); expect(warns[0].details).toMatchObject({ subnet: '10.42.0.0/24', expectedIpRange: '10.42.0.128/25', actualIpRange: null, }); const meshLines = warnSpy.mock.calls .map((args) => String(args[0])) .filter((line) => /\[Mesh\] sencho_mesh adopted without the upper-half IPAM IPRange/.test(line)); expect(meshLines.length).toBeGreaterThan(0); }); }); describe('MeshService console.log mirror (F-5)', () => { it('mirrors a setup failure to console.error with the [Mesh] prefix', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; process.env.HOSTNAME = 'sencho'; const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); mockDocker({ createNetwork: vi.fn().mockRejectedValue( Object.assign(new Error('Pool overlaps with other one on this address space'), { statusCode: 500 }), ), }); const svc = MeshService.getInstance(); await callSetup(svc); const meshLines = errSpy.mock.calls .map((args) => String(args[0])) .filter((line) => line.startsWith('[Mesh] data plane unavailable')); expect(meshLines.length).toBeGreaterThan(0); expect(meshLines[0]).toContain('subnet_overlap'); expect(meshLines[0]).toContain('10.42.0.0/24'); expect(meshLines[0]).toMatch(/overlap/i); }); it('mirrors a not_in_docker condition to console.warn (expected dev-mode case)', async () => { process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24'; delete process.env.HOSTNAME; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); mockDocker(); const svc = MeshService.getInstance(); await callSetup(svc); const meshLines = warnSpy.mock.calls .map((args) => String(args[0])) .filter((line) => line.startsWith('[Mesh] data plane unavailable')); expect(meshLines.length).toBeGreaterThan(0); expect(meshLines[0]).toContain('not_in_docker'); }); });