import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { stripSystemdSupervisorEnv } from '@electron/gateway/config-sync-env'; const { mockEnsureCloudSyncPluginInstalled, mockReadOpenClawConfig, mockWriteOpenClawConfig, mockLoggerWarn, } = vi.hoisted(() => ({ mockEnsureCloudSyncPluginInstalled: vi.fn(), mockReadOpenClawConfig: vi.fn(), mockWriteOpenClawConfig: vi.fn(), mockLoggerWarn: vi.fn(), })); vi.mock('@electron/utils/plugin-install', () => ({ copyPluginFromNodeModules: vi.fn(), cpSyncSafe: vi.fn(), ensureCloudSyncPluginInstalled: mockEnsureCloudSyncPluginInstalled, fixupPluginManifest: vi.fn(), })); vi.mock('@electron/utils/channel-config', () => ({ cleanupDanglingWeChatPluginState: vi.fn(), listConfiguredChannelsFromConfig: vi.fn(() => []), readOpenClawConfig: mockReadOpenClawConfig, writeOpenClawConfig: mockWriteOpenClawConfig, })); vi.mock('@electron/utils/logger', () => ({ logger: { info: vi.fn(), warn: mockLoggerWarn, error: vi.fn(), debug: vi.fn(), }, })); describe('stripSystemdSupervisorEnv', () => { it('removes systemd supervisor marker env vars', () => { const env = { PATH: '/usr/bin:/bin', OPENCLAW_SYSTEMD_UNIT: 'openclaw-gateway.service', INVOCATION_ID: 'abc123', SYSTEMD_EXEC_PID: '777', JOURNAL_STREAM: '8:12345', OTHER: 'keep-me', }; const result = stripSystemdSupervisorEnv(env); expect(result).toEqual({ PATH: '/usr/bin:/bin', OTHER: 'keep-me', }); }); it('keeps unrelated variables unchanged', () => { const env = { NODE_ENV: 'production', OPENCLAW_GATEWAY_TOKEN: 'token', CLAWDBOT_SKIP_CHANNELS: '0', }; expect(stripSystemdSupervisorEnv(env)).toEqual(env); }); it('does not mutate source env object', () => { const env = { OPENCLAW_SYSTEMD_UNIT: 'openclaw-gateway.service', VALUE: '1', }; const before = { ...env }; const result = stripSystemdSupervisorEnv(env); expect(env).toEqual(before); expect(result).toEqual({ VALUE: '1' }); }); }); describe('Cloud Sync launch config', () => { const originalEnv = process.env; beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); process.env = { ...originalEnv }; delete process.env.YINIAN_CLOUD_SYNC_ENABLED; delete process.env.YINIAN_CLOUD_SYNC_SERVER_URL; delete process.env.CLOUDCLAW_SERVER_URL; delete process.env.YINIAN_API_BASE_URL; mockEnsureCloudSyncPluginInstalled.mockReturnValue({ installed: true }); mockReadOpenClawConfig.mockResolvedValue({}); mockWriteOpenClawConfig.mockResolvedValue(undefined); }); afterEach(() => { process.env = originalEnv; }); it('derives the Cloud Sync server URL from explicit and API base env vars', async () => { const { deriveCloudSyncServerUrl } = await import('@electron/gateway/config-sync'); expect(deriveCloudSyncServerUrl()).toBe('https://onefeel.brother7.cn'); process.env.YINIAN_API_BASE_URL = 'https://onefeel.brother7.cn/ingress/'; expect(deriveCloudSyncServerUrl()).toBe('https://onefeel.brother7.cn'); process.env.YINIAN_CLOUD_SYNC_SERVER_URL = ' https://cloud.example.com/// '; expect(deriveCloudSyncServerUrl()).toBe('https://cloud.example.com'); }); it('writes the Cloud Sync plugin entry before Gateway launch', async () => { process.env.YINIAN_API_BASE_URL = 'https://onefeel.brother7.cn/ingress'; mockReadOpenClawConfig.mockResolvedValue({ plugins: { allow: ['existing-plugin'], entries: { 'cloud-sync': { enabled: false, config: { keep: 'value' }, }, }, }, }); const { ensureCloudSyncPluginConfigured } = await import('@electron/gateway/config-sync'); await ensureCloudSyncPluginConfigured(); expect(mockEnsureCloudSyncPluginInstalled).toHaveBeenCalled(); expect(mockWriteOpenClawConfig).toHaveBeenCalledWith({ plugins: { enabled: true, allow: ['existing-plugin', 'cloud-sync'], entries: { 'cloud-sync': { enabled: true, config: { keep: 'value', serverUrl: 'https://onefeel.brother7.cn', }, }, }, }, }); }); it('does not touch plugin config when Cloud Sync is disabled', async () => { process.env.YINIAN_CLOUD_SYNC_ENABLED = '0'; const { ensureCloudSyncPluginConfigured } = await import('@electron/gateway/config-sync'); await ensureCloudSyncPluginConfigured(); expect(mockEnsureCloudSyncPluginInstalled).not.toHaveBeenCalled(); expect(mockReadOpenClawConfig).not.toHaveBeenCalled(); expect(mockWriteOpenClawConfig).not.toHaveBeenCalled(); }); });