Add a packaged-client runtime boundary so the shared UI can talk to local, desktop, remote, and VS Code runtimes through the right transport instead of assuming one same-origin web server. Centralize OpenChamber-owned API access behind RuntimeAPIs, runtimeFetch, and runtime URL helpers, while keeping official OpenCode traffic on the SDK path. Support runtime switching, remote host selection, desktop client credentials, and headless connection links for pairing packaged clients with remote OpenChamber servers. Harden the new auth model by moving long-lived client tokens out of browser URLs, introducing short-lived scoped URL tokens for browser-owned transports, restricting URL-token access to explicit readable/realtime routes, and making client-token management session-scoped or self-scoped as appropriate. Update browser-owned assets and preview proxy flows to work with the split runtime model, including authenticated project icons, preview token propagation, CSP-safe preview bridge injection, and preview proxy auth that survives short-lived URL-token expiry. Tighten Electron security boundaries for packaged clients by gating privileged preload state to trusted origins and requiring explicit confirmation before connect deep-links import or switch remote runtimes. Also refresh agent guidance and project skills so future runtime/API, auth, preview, UI, CLI, settings, locale, and drag-to-reorder work follows the new architecture.
258 lines
10 KiB
TypeScript
258 lines
10 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { getDesktopRecoveryConfig } from './desktopRecoveryConfig';
|
|
|
|
describe('getDesktopRecoveryConfig', () => {
|
|
// ---------------------------------------------------------------------------
|
|
// 1. local-unavailable: both actions visible + retry labeled "Retry Local"
|
|
// ---------------------------------------------------------------------------
|
|
test('local-unavailable exposes both actions and Retry Local', () => {
|
|
const config = getDesktopRecoveryConfig('local-unavailable');
|
|
|
|
expect(config.title).toBe('Local OpenCode Unavailable');
|
|
expect(config.iconKey).toBe('local');
|
|
expect(config.showRetry).toBe(true);
|
|
expect(config.retryLabel).toBe('Retry Local');
|
|
expect(config.showUseLocal).toBe(true);
|
|
expect(config.showUseRemote).toBe(true);
|
|
// local-unavailable uses setup-oriented label since local needs installing
|
|
expect(config.useLocalLabel).toBe('Set Up Local');
|
|
expect(config.useRemoteLabel).toBe('Use Remote');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 2. remote-unreachable: both actions + retry
|
|
// ---------------------------------------------------------------------------
|
|
test('remote-unreachable exposes both actions + retry', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
'My Server',
|
|
'https://example.com:4096',
|
|
);
|
|
|
|
expect(config.title).toBe('Remote Server Unreachable');
|
|
expect(config.iconKey).toBe('remote');
|
|
expect(config.showRetry).toBe(true);
|
|
expect(config.retryLabel).toBe('Retry Connection');
|
|
expect(config.showUseLocal).toBe(true);
|
|
expect(config.showUseRemote).toBe(true);
|
|
// remote variants keep standard "Use Local"
|
|
expect(config.useLocalLabel).toBe('Use Local');
|
|
expect(config.useRemoteLabel).toBe('Use Remote');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 3. remote-wrong-service: both actions, NO retry
|
|
// ---------------------------------------------------------------------------
|
|
test('remote-wrong-service exposes both actions and no retry', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-wrong-service',
|
|
'Bad Host',
|
|
'https://wrong.example.com',
|
|
);
|
|
|
|
expect(config.title).toBe('Incompatible Server');
|
|
expect(config.iconKey).toBe('remote');
|
|
expect(config.showRetry).toBe(false);
|
|
expect(config.retryLabel).toBe(undefined);
|
|
expect(config.showUseLocal).toBe(true);
|
|
expect(config.showUseRemote).toBe(true);
|
|
expect(config.useLocalLabel).toBe('Use Local');
|
|
expect(config.useRemoteLabel).toBe('Use Remote');
|
|
});
|
|
|
|
test('remote-incompatible exposes retry and both actions', () => {
|
|
const config = getDesktopRecoveryConfig('remote-incompatible', 'Old Server', 'https://old.example');
|
|
|
|
expect(config.showRetry).toBe(true);
|
|
expect(config.showUseLocal).toBe(true);
|
|
expect(config.showUseRemote).toBe(true);
|
|
expect(config.titleKey).toBe('onboarding.desktopRecovery.remoteIncompatible.title');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 4. missing-default-host: chooser-with-context (both actions, no retry)
|
|
// ---------------------------------------------------------------------------
|
|
test('missing-default-host behaves like chooser-with-context', () => {
|
|
const config = getDesktopRecoveryConfig('missing-default-host');
|
|
|
|
expect(config.title).toBe('No Default Connection');
|
|
expect(config.iconKey).toBe('local');
|
|
expect(config.showRetry).toBe(false);
|
|
expect(config.retryLabel).toBe(undefined);
|
|
expect(config.showUseLocal).toBe(true);
|
|
expect(config.showUseRemote).toBe(true);
|
|
expect(config.useLocalLabel).toBe('Use Local');
|
|
expect(config.useRemoteLabel).toBe('Use Remote');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 5. descriptions redact sensitive query params for remote variants
|
|
// ---------------------------------------------------------------------------
|
|
test('remote-unreachable description redacts sensitive query params in URL', () => {
|
|
const sensitiveUrl =
|
|
'https://example.com:4096?token=super-secret&auth=abc123';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
undefined,
|
|
sensitiveUrl,
|
|
);
|
|
|
|
// Secrets must never appear in the description
|
|
expect(config.description).not.toContain('super-secret');
|
|
expect(config.description).not.toContain('abc123');
|
|
// Redaction marker is present
|
|
expect(config.description).toContain('REDACTED');
|
|
expect(config.description).toContain('example.com');
|
|
});
|
|
|
|
test('remote-wrong-service description redacts sensitive query params in URL', () => {
|
|
const sensitiveUrl =
|
|
'https://wrong.example.com?api_key=sk-12345&secret=mysecret';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-wrong-service',
|
|
undefined,
|
|
sensitiveUrl,
|
|
);
|
|
|
|
// Secrets must never appear in the description
|
|
expect(config.description).not.toContain('sk-12345');
|
|
expect(config.description).not.toContain('mysecret');
|
|
// Redaction marker is present
|
|
expect(config.description).toContain('REDACTED');
|
|
expect(config.description).toContain('wrong.example.com');
|
|
});
|
|
|
|
test('local-unavailable description does not reference host URL', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'local-unavailable',
|
|
'Some Host',
|
|
'https://example.com?token=secret',
|
|
);
|
|
|
|
// local-unavailable ignores hostUrl in its description
|
|
expect(config.description).not.toContain('example.com');
|
|
expect(config.description).not.toContain('secret');
|
|
});
|
|
|
|
test('missing-default-host description does not reference host URL', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'missing-default-host',
|
|
'Some Host',
|
|
'https://example.com?token=secret',
|
|
);
|
|
|
|
expect(config.description).not.toContain('example.com');
|
|
expect(config.description).not.toContain('secret');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 6. URL-like hostLabel is also redacted (sensitive data leak prevention)
|
|
// ---------------------------------------------------------------------------
|
|
test('remote-unreachable redacts URL-like hostLabel containing sensitive query params', () => {
|
|
const urlAsLabel =
|
|
'https://example.com:4096?token=super-secret&auth=abc123';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
urlAsLabel,
|
|
'https://fallback.example.com',
|
|
);
|
|
|
|
// Secrets in hostLabel must never appear in the description
|
|
expect(config.description).not.toContain('super-secret');
|
|
expect(config.description).not.toContain('abc123');
|
|
// Redaction marker is present
|
|
expect(config.description).toContain('REDACTED');
|
|
expect(config.description).toContain('example.com');
|
|
});
|
|
|
|
test('remote-wrong-service redacts URL-like hostLabel containing sensitive query params', () => {
|
|
const urlAsLabel =
|
|
'https://wrong.example.com?api_key=sk-12345&secret=mysecret';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-wrong-service',
|
|
urlAsLabel,
|
|
'https://fallback.example.com',
|
|
);
|
|
|
|
// Secrets in hostLabel must never appear in the description
|
|
expect(config.description).not.toContain('sk-12345');
|
|
expect(config.description).not.toContain('mysecret');
|
|
// Redaction marker is present
|
|
expect(config.description).toContain('REDACTED');
|
|
expect(config.description).toContain('wrong.example.com');
|
|
});
|
|
|
|
test('remote-unreachable redacts embedded credentials in URL-like hostLabel', () => {
|
|
const urlWithCreds = 'https://admin:s3cret@example.com:4096';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
urlWithCreds,
|
|
'https://fallback.example.com',
|
|
);
|
|
|
|
// Username/password must never appear in the description
|
|
expect(config.description).not.toContain('admin');
|
|
expect(config.description).not.toContain('s3cret');
|
|
// Hostname should still be visible
|
|
expect(config.description).toContain('example.com');
|
|
});
|
|
|
|
test('remote-wrong-service redacts embedded credentials in URL-like hostLabel', () => {
|
|
const urlWithCreds = 'https://user:pass123@wrong.example.com';
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-wrong-service',
|
|
urlWithCreds,
|
|
'https://fallback.example.com',
|
|
);
|
|
|
|
expect(config.description).not.toContain('user');
|
|
expect(config.description).not.toContain('pass123');
|
|
expect(config.description).toContain('wrong.example.com');
|
|
});
|
|
|
|
test('non-URL hostLabel is used as-is without redaction', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
'My Server',
|
|
'https://example.com?token=secret',
|
|
);
|
|
|
|
// Plain label should appear verbatim
|
|
expect(config.description).toContain('My Server');
|
|
// hostUrl secrets should not leak (already tested above, but sanity check)
|
|
expect(config.description).not.toContain('secret');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fallback descriptions when no host info is provided
|
|
// ---------------------------------------------------------------------------
|
|
test('remote-unreachable falls back to generic text when no host info', () => {
|
|
const config = getDesktopRecoveryConfig('remote-unreachable');
|
|
|
|
expect(config.description).toContain('the remote server');
|
|
expect(config.description).not.toContain('undefined');
|
|
});
|
|
|
|
test('remote-wrong-service falls back to generic text when no host info', () => {
|
|
const config = getDesktopRecoveryConfig('remote-wrong-service');
|
|
|
|
expect(config.description).toContain('unknown');
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Whitespace-only hostLabel is treated as absent
|
|
// ---------------------------------------------------------------------------
|
|
test('whitespace-only hostLabel falls back to hostUrl', () => {
|
|
const config = getDesktopRecoveryConfig(
|
|
'remote-unreachable',
|
|
' ',
|
|
'https://fallback.example.com?token=secret',
|
|
);
|
|
|
|
// hostLabel is whitespace-only → should use redacted hostUrl instead
|
|
expect(config.description).not.toContain('secret');
|
|
expect(config.description).toContain('fallback.example.com');
|
|
expect(config.description).toContain('REDACTED');
|
|
});
|
|
});
|