mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: remove 78 low-value cases and four entire suites (#34687)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -21198,7 +21198,7 @@
|
||||
"invariant": "A packaged macOS daemon runs from the verified signed helper copy for its build, which the updater never touches, and never beside a job that may own the endpoint; the fork fallback still fits the startup gate. A copy is deleted only when lsof proves it unused.",
|
||||
"oracle": "Deterministic launch, fallback-deadline, per-build copy, retirement and status tests, plus the signed launcher validation recorded in #25848.",
|
||||
"commands": [
|
||||
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/macos-daemon-bundle.test.ts src/main/daemon/macos-daemon-launchd.test.ts src/main/daemon/daemon-init-macos-stable-launch-fallback.test.ts src/main/daemon/macos-daemon-job-state.test.ts src/main/daemon/macos-daemon-bundle-retirement.test.ts src/main/daemon/daemon-full-disk-access-status.test.ts src/main/daemon/daemon-pty-adapter-full-disk-access.test.ts config/scripts/macos-folder-usage-descriptions.test.mjs src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx"
|
||||
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/macos-daemon-bundle.test.ts src/main/daemon/macos-daemon-launchd.test.ts src/main/daemon/daemon-init-macos-stable-launch-fallback.test.ts src/main/daemon/macos-daemon-job-state.test.ts src/main/daemon/macos-daemon-bundle-retirement.test.ts src/main/daemon/daemon-full-disk-access-status.test.ts config/scripts/macos-folder-usage-descriptions.test.mjs src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx"
|
||||
],
|
||||
"testFiles": [
|
||||
"src/main/daemon/macos-daemon-bundle.test.ts",
|
||||
@@ -21207,7 +21207,6 @@
|
||||
"src/main/daemon/macos-daemon-job-state.test.ts",
|
||||
"src/main/daemon/macos-daemon-bundle-retirement.test.ts",
|
||||
"src/main/daemon/daemon-full-disk-access-status.test.ts",
|
||||
"src/main/daemon/daemon-pty-adapter-full-disk-access.test.ts",
|
||||
"config/scripts/macos-folder-usage-descriptions.test.mjs",
|
||||
"src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx"
|
||||
],
|
||||
@@ -21241,6 +21240,7 @@
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/macos-daemon-bundle.test.ts src/main/daemon/macos-daemon-launchd.test.ts src/main/daemon/daemon-init-macos-stable-launch-fallback.test.ts src/main/daemon/macos-daemon-job-state.test.ts src/main/daemon/macos-daemon-bundle-retirement.test.ts src/main/daemon/daemon-full-disk-access-status.test.ts src/main/daemon/daemon-pty-adapter-full-disk-access.test.ts config/scripts/macos-folder-usage-descriptions.test.mjs src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx",
|
||||
"currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/daemon/macos-daemon-bundle.test.ts src/main/daemon/macos-daemon-launchd.test.ts src/main/daemon/daemon-init-macos-stable-launch-fallback.test.ts src/main/daemon/macos-daemon-job-state.test.ts src/main/daemon/macos-daemon-bundle-retirement.test.ts src/main/daemon/daemon-full-disk-access-status.test.ts config/scripts/macos-folder-usage-descriptions.test.mjs src/renderer/src/components/settings/DeveloperPermissionsPane.test.tsx",
|
||||
"result": "passed",
|
||||
"durationSeconds": 2.38,
|
||||
"summary": "All 82 focused launch, fallback-deadline, per-build copy, retirement, status and packaging tests passed."
|
||||
|
||||
@@ -2125,7 +2125,6 @@
|
||||
"src/main/daemon/daemon-pty-adapter-cold-restore-seed.test.ts": 751,
|
||||
"src/main/daemon/daemon-pty-adapter-concurrent-recovery.test.ts": 700,
|
||||
"src/main/daemon/daemon-pty-adapter-daemon-recovery.test.ts": 1326,
|
||||
"src/main/daemon/daemon-pty-adapter-full-disk-access.test.ts": 505,
|
||||
"src/main/daemon/daemon-pty-adapter-history-checkpoints.test.ts": 1460,
|
||||
"src/main/daemon/daemon-pty-adapter-history-recovery.test.ts": 1078,
|
||||
"src/main/daemon/daemon-pty-adapter-history-shutdown-admission.test.ts": 671,
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { DaemonClient } from './client'
|
||||
import { DaemonPtyAdapter } from './daemon-pty-adapter'
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
it.each([
|
||||
[{ status: 'granted' }, 'granted'],
|
||||
[{ status: 'denied' }, 'denied'],
|
||||
[{ status: 'future-status' }, 'unknown'],
|
||||
[null, 'unknown']
|
||||
])('checks the owning connection and validates %j', async (response, expected) => {
|
||||
vi.spyOn(DaemonClient.prototype, 'isConnected').mockReturnValue(true)
|
||||
const request = vi.spyOn(DaemonClient.prototype, 'request').mockResolvedValue(response)
|
||||
const adapter = new DaemonPtyAdapter({ socketPath: '/unused', tokenPath: '/unused' })
|
||||
expect(await adapter.getFullDiskAccessStatus()).toBe(expected)
|
||||
expect(request).toHaveBeenCalledWith('fullDiskAccessStatus', undefined, 3000)
|
||||
})
|
||||
|
||||
it.each(['Unknown request type', 'Request timed out', 'Connection lost'])(
|
||||
'keeps old or unreachable hosts unknown: %s',
|
||||
async (message) => {
|
||||
vi.spyOn(DaemonClient.prototype, 'isConnected').mockReturnValue(true)
|
||||
vi.spyOn(DaemonClient.prototype, 'request').mockRejectedValue(new Error(message))
|
||||
const adapter = new DaemonPtyAdapter({ socketPath: '/unused', tokenPath: '/unused' })
|
||||
expect(await adapter.getFullDiskAccessStatus()).toBe('unknown')
|
||||
}
|
||||
)
|
||||
|
||||
it('does not reconnect or respawn a disconnected host to obtain a verdict', async () => {
|
||||
const request = vi.spyOn(DaemonClient.prototype, 'request')
|
||||
const connect = vi.spyOn(DaemonClient.prototype, 'ensureConnected')
|
||||
const adapter = new DaemonPtyAdapter({ socketPath: '/unused', tokenPath: '/unused' })
|
||||
expect(await adapter.getFullDiskAccessStatus()).toBe('unknown')
|
||||
expect(request).not.toHaveBeenCalled()
|
||||
expect(connect).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -1,18 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { normalizeLinkedPullRequests } from './project-view/project-view-field-normalization'
|
||||
describe('normalizeLinkedPullRequests linked PR values', () => {
|
||||
it('normalizes linked pull requests with a visible overflow count', () => {
|
||||
const value = normalizeLinkedPullRequests({
|
||||
__typename: 'ProjectV2ItemFieldPullRequestValue',
|
||||
field: { id: 'field-pr', name: 'Linked pull requests', dataType: 'LINKED_PULL_REQUESTS' },
|
||||
pullRequests: {
|
||||
totalCount: 12,
|
||||
nodes: [{ number: 12, title: 'Fix', url: 'https://github.com/o/r/pull/12' }, null]
|
||||
}
|
||||
})
|
||||
expect(value).toEqual({
|
||||
pullRequests: [{ number: 12, title: 'Fix', url: 'https://github.com/o/r/pull/12' }],
|
||||
totalCount: 12
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { RuntimeClientSettingsController } from './runtime-client-settings'
|
||||
import { createGlobalSettingsFixture } from '../../shared/global-settings-test-fixture'
|
||||
import type { GlobalSettings } from '../../shared/global-settings-types'
|
||||
|
||||
// Missing nativeChatQueueFollowUps looks like an older host to mobile, which then queues mid-turn sends.
|
||||
function projectionOf(settings: Partial<GlobalSettings>) {
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: get() reads nothing but store.getSettings(); every other RuntimeStore member is unreachable from that path.
|
||||
return new RuntimeClientSettingsController({ getSettings: () => settings } as never).get()
|
||||
}
|
||||
|
||||
function hostSettings(overrides: Partial<GlobalSettings>): Partial<GlobalSettings> {
|
||||
return { ...createGlobalSettingsFixture({ workspaceDir: '/w' }), ...overrides }
|
||||
}
|
||||
|
||||
describe('RuntimeClientSettingsController queue follow-ups projection', () => {
|
||||
it('publishes the opt-out to paired clients', () => {
|
||||
expect(
|
||||
projectionOf(hostSettings({ nativeChatQueueFollowUps: false })).nativeChatQueueFollowUps
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('reports on when the host has no persisted preference', () => {
|
||||
const settings = hostSettings({})
|
||||
delete settings.nativeChatQueueFollowUps
|
||||
expect(projectionOf(settings).nativeChatQueueFollowUps).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { MessageBoxOptions, MessageBoxReturnValue } from 'electron'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { confirmQuitAndStopAllSessions } from './quit-and-stop-all-sessions-prompt'
|
||||
|
||||
function showMessageBoxAnswering(response: number) {
|
||||
return vi.fn(async (_options: MessageBoxOptions): Promise<MessageBoxReturnValue> => ({
|
||||
response,
|
||||
checkboxChecked: false
|
||||
}))
|
||||
}
|
||||
|
||||
describe('confirmQuitAndStopAllSessions', () => {
|
||||
it('confirms only when the user picks Stop Sessions and Quit', async () => {
|
||||
await expect(confirmQuitAndStopAllSessions(showMessageBoxAnswering(0))).resolves.toBe(true)
|
||||
await expect(confirmQuitAndStopAllSessions(showMessageBoxAnswering(1))).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('makes Cancel the default so Return or Escape never stops running agents', async () => {
|
||||
const showMessageBox = showMessageBoxAnswering(1)
|
||||
|
||||
await confirmQuitAndStopAllSessions(showMessageBox)
|
||||
|
||||
const options = showMessageBox.mock.calls[0][0]
|
||||
expect(options.buttons).toEqual(['Stop Sessions and Quit', 'Cancel'])
|
||||
expect(options.defaultId).toBe(1)
|
||||
expect(options.cancelId).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { eventSchemas } from '../../shared/telemetry-events'
|
||||
|
||||
import { persistBannerAcknowledgeWithoutEmitting, track, trackAppOpenedOnce } from './client'
|
||||
import {
|
||||
cleanupTelemetryClientTest,
|
||||
@@ -45,28 +45,6 @@ describe('Agent Dashboard app-open snapshot', () => {
|
||||
expect(state.mock.capture.mock.calls[0]?.[0].properties.agent_dashboard_enabled).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['opt_out', 'pending_banner', 'do_not_track', 'orca_disabled', 'ci'])(
|
||||
'sends no snapshot when consent is blocked by %s',
|
||||
(block) => {
|
||||
state.settings.experimentalAgentDashboardPopout = true
|
||||
if (block === 'opt_out') {
|
||||
state.settings.telemetry!.optedIn = false
|
||||
} else if (block === 'pending_banner') {
|
||||
state.settings.telemetry!.optedIn = null
|
||||
} else if (block === 'do_not_track') {
|
||||
process.env.DO_NOT_TRACK = '1'
|
||||
} else if (block === 'orca_disabled') {
|
||||
process.env.ORCA_TELEMETRY_DISABLED = '1'
|
||||
} else {
|
||||
process.env.CI = '1'
|
||||
}
|
||||
|
||||
trackAppOpenedOnce()
|
||||
|
||||
expect(state.mock.capture).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it('reads the latest setting when the pending banner is resolved', async () => {
|
||||
state.settings.telemetry!.optedIn = null
|
||||
state.settings.telemetry!.existedBeforeTelemetryRelease = true
|
||||
@@ -84,12 +62,4 @@ describe('Agent Dashboard app-open snapshot', () => {
|
||||
properties: { agent_dashboard_enabled: true }
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the app-open schema compatible with older payloads and rejects non-boolean state', () => {
|
||||
expect(eventSchemas.app_opened.safeParse({}).success).toBe(true)
|
||||
expect(eventSchemas.app_opened.safeParse({ agent_dashboard_enabled: true }).success).toBe(true)
|
||||
expect(eventSchemas.app_opened.safeParse({ agent_dashboard_enabled: 'true' }).success).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import type { PostHog } from 'posthog-node'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
_enableTransportForTests,
|
||||
_setPostHogClientForTests,
|
||||
_setShuttingDownForTests,
|
||||
persistBannerAcknowledgeWithoutEmitting,
|
||||
setOptIn,
|
||||
shouldOptOutSdkAtInit,
|
||||
shutdownTelemetry
|
||||
} from './client'
|
||||
import { persistBannerAcknowledgeWithoutEmitting, setOptIn } from './client'
|
||||
import {
|
||||
BASE_COMMON,
|
||||
cleanupTelemetryClientTest,
|
||||
makeMockPostHog,
|
||||
setupTelemetryClientTest,
|
||||
type MockPostHog,
|
||||
type TelemetryClientTestState
|
||||
@@ -66,17 +56,6 @@ describe('setOptIn()', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('drops telemetry_opted_in silently in non-official builds', async () => {
|
||||
state.settings.telemetry!.optedIn = false
|
||||
_setPostHogClientForTests(null)
|
||||
_enableTransportForTests(false)
|
||||
|
||||
await setOptIn('settings', true)
|
||||
|
||||
expect(mock.capture).not.toHaveBeenCalled()
|
||||
expect(console.debug).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('fires app_opened once after pending-banner opt-in enables the SDK', async () => {
|
||||
state.settings.telemetry = {
|
||||
optedIn: null,
|
||||
@@ -129,41 +108,3 @@ describe('persistBannerAcknowledgeWithoutEmitting()', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// pending_banner must not opt out the SDK before the banner Turn Off capture can enqueue.
|
||||
describe('shouldOptOutSdkAtInit()', () => {
|
||||
it('opts out the SDK for every disabled-reason', () => {
|
||||
for (const reason of ['user_opt_out', 'ci', 'do_not_track', 'orca_disabled'] as const) {
|
||||
expect(shouldOptOutSdkAtInit({ effective: 'disabled', reason })).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('does NOT opt out the SDK for pending_banner', () => {
|
||||
expect(shouldOptOutSdkAtInit({ effective: 'pending_banner' })).toBe(false)
|
||||
})
|
||||
|
||||
it('does NOT opt out the SDK for enabled', () => {
|
||||
expect(shouldOptOutSdkAtInit({ effective: 'enabled' })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shutdownTelemetry()', () => {
|
||||
afterEach(() => {
|
||||
_setShuttingDownForTests(false)
|
||||
_setPostHogClientForTests(null)
|
||||
})
|
||||
|
||||
it('sets the shutdown gate and calls posthog.shutdown(2000)', async () => {
|
||||
const mock = makeMockPostHog()
|
||||
_setPostHogClientForTests(mock as unknown as PostHog)
|
||||
_setShuttingDownForTests(false)
|
||||
await shutdownTelemetry()
|
||||
expect(mock.shutdown).toHaveBeenCalledWith(2_000)
|
||||
_setPostHogClientForTests(null)
|
||||
})
|
||||
|
||||
it('is a no-op when no client is initialized', async () => {
|
||||
_setPostHogClientForTests(null)
|
||||
await expect(shutdownTelemetry()).resolves.toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
import type { GlobalSettings } from '../../shared/global-settings-types'
|
||||
import { resolveConsent, _resetMisconfigWarnCacheForTests } from './consent'
|
||||
|
||||
@@ -146,31 +146,6 @@ describe('resolveConsent', () => {
|
||||
).toEqual({ effective: 'enabled' })
|
||||
})
|
||||
|
||||
it('warns to stderr once for misconfigured env var values like "yes" / "on" / "FALSE"', () => {
|
||||
const spy = vi.spyOn(process.stderr, 'write').mockReturnValue(true)
|
||||
try {
|
||||
process.env.DO_NOT_TRACK = 'yes'
|
||||
resolveConsent(
|
||||
settingsWithTelemetry({
|
||||
optedIn: true,
|
||||
installId: 'x',
|
||||
existedBeforeTelemetryRelease: false
|
||||
})
|
||||
)
|
||||
resolveConsent(
|
||||
settingsWithTelemetry({
|
||||
optedIn: true,
|
||||
installId: 'x',
|
||||
existedBeforeTelemetryRelease: false
|
||||
})
|
||||
)
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
expect(spy.mock.calls[0][0]).toContain('DO_NOT_TRACK')
|
||||
} finally {
|
||||
spy.mockRestore()
|
||||
}
|
||||
})
|
||||
|
||||
it('treats a misconfigured DO_NOT_TRACK=0 as unset (not truthy)', () => {
|
||||
// String zero must not disable telemetry through ordinary JavaScript truthiness.
|
||||
process.env.DO_NOT_TRACK = '0'
|
||||
|
||||
@@ -73,21 +73,6 @@ describe('onboarding-cohort-classifier', () => {
|
||||
expect(getOnboardingCohortAtEmit()).toEqual({ cohort: 'upgrade_backfill' })
|
||||
})
|
||||
|
||||
it('classifies a live-completed existing user as upgrade_backfill (known limitation)', () => {
|
||||
// Migration backfill and existing-user live completion share one shape; dashboards must forward-fill cohort from session _started events.
|
||||
initOnboardingCohortClassifier(
|
||||
makeFakeStore({
|
||||
existedBefore: true,
|
||||
onboarding: {
|
||||
outcome: 'completed',
|
||||
lastCompletedStep: ONBOARDING_FINAL_STEP,
|
||||
closedAt: 1234567890
|
||||
}
|
||||
})
|
||||
)
|
||||
expect(getOnboardingCohortAtEmit()).toEqual({ cohort: 'upgrade_backfill' })
|
||||
})
|
||||
|
||||
it('classifies an existing user mid-wizard as fresh_install (the funnel cohort)', () => {
|
||||
// Without canonical backfill completion, existing users still going through the wizard belong to fresh_install.
|
||||
initOnboardingCohortClassifier(
|
||||
@@ -104,10 +89,6 @@ describe('onboarding-cohort-classifier', () => {
|
||||
expect(getOnboardingCohortAtEmit()).toEqual({ cohort: undefined })
|
||||
})
|
||||
|
||||
it('returns undefined when the store is not initialized', () => {
|
||||
expect(getOnboardingCohortAtEmit()).toEqual({ cohort: undefined })
|
||||
})
|
||||
|
||||
it('never throws and returns undefined when getSettings throws', () => {
|
||||
initOnboardingCohortClassifier(makeFakeStore({ existedBefore: false, throwOnSettings: true }))
|
||||
expect(() => getOnboardingCohortAtEmit()).not.toThrow()
|
||||
@@ -119,13 +100,4 @@ describe('onboarding-cohort-classifier', () => {
|
||||
expect(() => getOnboardingCohortAtEmit()).not.toThrow()
|
||||
expect(getOnboardingCohortAtEmit()).toEqual({ cohort: undefined })
|
||||
})
|
||||
|
||||
it('warns at most once per session even across many degraded calls', () => {
|
||||
initOnboardingCohortClassifier(makeFakeStore({ existedBefore: false, throwOnSettings: true }))
|
||||
const warnSpy = console.warn as unknown as ReturnType<typeof vi.spyOn>
|
||||
for (let i = 0; i < 50; i++) {
|
||||
getOnboardingCohortAtEmit()
|
||||
}
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,29 +38,6 @@ describe('onboarding feature setup telemetry validation', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects raw strings and unknown fields', () => {
|
||||
expect(
|
||||
validate('onboarding_feature_setup_toggled', {
|
||||
feature: 'browser_use',
|
||||
selected: false,
|
||||
path: '/Users/alice/project'
|
||||
}).ok
|
||||
).toBe(false)
|
||||
expect(
|
||||
validate('onboarding_feature_setup_run', {
|
||||
browser_use: true,
|
||||
computer_use: false,
|
||||
linear_tickets: false,
|
||||
orchestration: false,
|
||||
selected_count: 1,
|
||||
cli_touched: false,
|
||||
skill_install_result: 'Installed · 1 left alone',
|
||||
computer_use_permissions_opened: false,
|
||||
warning_count: 0
|
||||
}).ok
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects selected_count values that do not match selected features', () => {
|
||||
expect(
|
||||
validate('onboarding_feature_setup_run', {
|
||||
|
||||
@@ -10,67 +10,6 @@ describe('validate', () => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('accepts a well-formed app_opened payload', () => {
|
||||
const result = validate('app_opened', {})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a well-formed agent_started payload', () => {
|
||||
const result = validate('agent_started', {
|
||||
agent_kind: 'claude-code',
|
||||
launch_source: 'command_palette',
|
||||
request_kind: 'new'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a well-formed agent_prompt_sent payload', () => {
|
||||
const result = validate('agent_prompt_sent', {
|
||||
agent_kind: 'claude-code',
|
||||
launch_source: 'unknown',
|
||||
request_kind: 'followup',
|
||||
nth_repo_added: 1
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a well-formed star_nag_outcome payload with cohort context', () => {
|
||||
const result = validate('star_nag_outcome', {
|
||||
outcome: 'opened_repo',
|
||||
source: 'force_show',
|
||||
mode: 'web',
|
||||
threshold: 35,
|
||||
agents_since_baseline: 42,
|
||||
agents_since_baseline_bucket: '35-69',
|
||||
nth_repo_added: 4
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects malformed star_nag_outcome payloads', () => {
|
||||
expect(
|
||||
validate('star_nag_outcome', {
|
||||
outcome: 'opened_repo',
|
||||
source: 'force_show',
|
||||
mode: 'web',
|
||||
threshold: 35,
|
||||
agents_since_baseline: 42,
|
||||
agents_since_baseline_bucket: '35-69',
|
||||
raw_error: 'nope'
|
||||
} as never).ok
|
||||
).toBe(false)
|
||||
expect(
|
||||
validate('star_nag_outcome', {
|
||||
outcome: 'opened_repo',
|
||||
source: 'force_show',
|
||||
mode: 'web',
|
||||
threshold: 0,
|
||||
agents_since_baseline: 42,
|
||||
agents_since_baseline_bucket: '35-69'
|
||||
} as never).ok
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('drops unknown event names', () => {
|
||||
const result = validate('not_a_real_event' as never, {})
|
||||
expect(result.ok).toBe(false)
|
||||
@@ -78,365 +17,4 @@ describe('validate', () => {
|
||||
expect(result.reason).toMatch(/unknown event/)
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects extra keys via .strict()', () => {
|
||||
const result = validate('app_opened', { unexpected: 'value' })
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
// Raw agent error strings must fail schema validation and never transmit.
|
||||
it('rejects error_message on agent_error', () => {
|
||||
const result = validate('agent_error', {
|
||||
error_class: 'unknown',
|
||||
agent_kind: 'claude-code',
|
||||
error_message: 'at /Users/alice/secret/path/index.ts:42'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects error_stack on agent_error', () => {
|
||||
const result = validate('agent_error', {
|
||||
error_class: 'unknown',
|
||||
agent_kind: 'claude-code',
|
||||
error_stack: 'Error: boom\n at /Users/alice/...'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('drops on missing required key', () => {
|
||||
const result = validate('agent_started', {
|
||||
agent_kind: 'claude-code',
|
||||
launch_source: 'sidebar'
|
||||
// missing request_kind
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('drops on wrong enum value', () => {
|
||||
const result = validate('agent_started', {
|
||||
agent_kind: 'claude-code',
|
||||
launch_source: 'command_palette',
|
||||
request_kind: 'restart' // not in ['new', 'resume', 'followup']
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts a well-formed agent_error payload', () => {
|
||||
const result = validate('agent_error', {
|
||||
error_class: 'binary_not_found',
|
||||
agent_kind: 'claude-code'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects agent_error with a deferred enum value', () => {
|
||||
const result = validate('agent_error', {
|
||||
error_class: 'auth_expired',
|
||||
agent_kind: 'claude-code'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
// Optional cohort permits degraded undefined; reject negative/noninteger/string shapes (onboarding-funnel-cohort-addendum.md).
|
||||
|
||||
it('accepts app_opened with nth_repo_added=0 (the session-zero cohort signal)', () => {
|
||||
const result = validate('app_opened', { nth_repo_added: 0 })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts repo_added with nth_repo_added=1', () => {
|
||||
const result = validate('repo_added', { method: 'folder_picker', nth_repo_added: 1 })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
// Git/folder evidence belongs to repo_added after project selection left onboarding; optional for remote edges.
|
||||
|
||||
it('accepts repo_added with is_git_repo=true', () => {
|
||||
const result = validate('repo_added', { method: 'clone_url', is_git_repo: true })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts repo_added with is_git_repo=false', () => {
|
||||
const result = validate('repo_added', { method: 'folder_picker', is_git_repo: false })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts repo_added without is_git_repo (SSH/remote degraded mode)', () => {
|
||||
const result = validate('repo_added', { method: 'folder_picker' })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects non-boolean is_git_repo on repo_added', () => {
|
||||
const result = validate('repo_added', {
|
||||
method: 'folder_picker',
|
||||
is_git_repo: 'yes'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects the retired is_git_repo field on onboarding_completed', () => {
|
||||
// Strict onboarding-completed schema must reject the retired Git-project field.
|
||||
const result = validate('onboarding_completed', {
|
||||
path: 'add_project_modal',
|
||||
is_git_repo: false,
|
||||
total_duration_ms: 100
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_completed without is_git_repo', () => {
|
||||
const result = validate('onboarding_completed', {
|
||||
path: 'add_project_modal',
|
||||
total_duration_ms: 100
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects negative nth_repo_added', () => {
|
||||
const result = validate('app_opened', { nth_repo_added: -1 } as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects non-integer nth_repo_added', () => {
|
||||
const result = validate('app_opened', { nth_repo_added: 1.5 } as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects nth_repo_added on a non-cohort event (settings_changed)', () => {
|
||||
// Undeclared injected cohort fields must drop; IPC therefore injects only where schema declares them.
|
||||
const result = validate('settings_changed', {
|
||||
setting_key: 'editorAutoSave',
|
||||
value_kind: 'bool',
|
||||
nth_repo_added: 1
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_agent_picked with all required fields', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'claude-code',
|
||||
on_path: true,
|
||||
detected_count: 2,
|
||||
detection_state: 'complete',
|
||||
from_collapsed_section: false
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts onboarding_agent_picked with cohort injected', () => {
|
||||
// Optional cohort injection must remain compatible with strict schemas.
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'codex',
|
||||
on_path: false,
|
||||
detected_count: 0,
|
||||
detection_state: 'pending',
|
||||
from_collapsed_section: true,
|
||||
cohort: 'fresh_install'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_agent_picked with unknown detection_state', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'claude-code',
|
||||
on_path: true,
|
||||
detected_count: 1,
|
||||
detection_state: 'detecting',
|
||||
from_collapsed_section: false
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_agent_picked with path_source and path_failure_reason', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'claude-code',
|
||||
on_path: false,
|
||||
detected_count: 0,
|
||||
detection_state: 'complete',
|
||||
from_collapsed_section: false,
|
||||
path_source: 'sync_seed_only',
|
||||
path_failure_reason: 'timeout'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts onboarding_agent_picked without the new optional path fields', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'codex',
|
||||
on_path: true,
|
||||
detected_count: 2,
|
||||
detection_state: 'complete',
|
||||
from_collapsed_section: false
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_agent_picked with unknown path_source', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'claude-code',
|
||||
on_path: true,
|
||||
detected_count: 1,
|
||||
detection_state: 'complete',
|
||||
from_collapsed_section: false,
|
||||
path_source: 'env_path'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects onboarding_agent_picked with unknown path_failure_reason', () => {
|
||||
const result = validate('onboarding_agent_picked', {
|
||||
agent_kind: 'claude-code',
|
||||
on_path: true,
|
||||
detected_count: 1,
|
||||
detection_state: 'complete',
|
||||
from_collapsed_section: false,
|
||||
path_failure_reason: 'parse_failed'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_ghostty_discovered with field_group_count_bucket', () => {
|
||||
const result = validate('onboarding_ghostty_discovered', {
|
||||
state: 'found',
|
||||
field_group_count_bucket: '4-7'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_ghostty_discovered with raw count instead of bucket', () => {
|
||||
// Raw environment counts fingerprint installs and must never transmit.
|
||||
const result = validate('onboarding_ghostty_discovered', {
|
||||
state: 'found',
|
||||
field_group_count_bucket: 5
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_ghostty_import_clicked with no payload', () => {
|
||||
const result = validate('onboarding_ghostty_import_clicked', {})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts onboarding_ghostty_import_failed with each enum reason', () => {
|
||||
for (const reason of ['no_config', 'empty_diff', 'unknown'] as const) {
|
||||
const result = validate('onboarding_ghostty_import_failed', { reason })
|
||||
expect(result.ok).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts onboarding_step_completed with duration_ms and advanced_via', () => {
|
||||
const result = validate('onboarding_step_completed', {
|
||||
step: 1,
|
||||
value_kind: 'agent',
|
||||
duration_ms: 1234,
|
||||
advanced_via: 'keyboard'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts onboarding_step_completed without the new optional fields', () => {
|
||||
// Optional additions keep pre-deploy events valid.
|
||||
const result = validate('onboarding_step_completed', {
|
||||
step: 2,
|
||||
value_kind: 'theme'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts the Windows terminal onboarding step value kind', () => {
|
||||
const result = validate('onboarding_step_completed', {
|
||||
step: 4,
|
||||
value_kind: 'windows_terminal'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_step_completed with negative duration_ms', () => {
|
||||
const result = validate('onboarding_step_completed', {
|
||||
step: 1,
|
||||
value_kind: 'agent',
|
||||
duration_ms: -5
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects onboarding_step_completed with unknown advanced_via', () => {
|
||||
const result = validate('onboarding_step_completed', {
|
||||
step: 1,
|
||||
value_kind: 'agent',
|
||||
advanced_via: 'voice'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_task_sources_snapshot with bounded statuses', () => {
|
||||
const result = validate('onboarding_task_sources_snapshot', {
|
||||
github_status: 'connected',
|
||||
linear_status: 'not_connected',
|
||||
exit_action: 'continue',
|
||||
duration_ms: 1200,
|
||||
advanced_via: 'button'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_task_sources_snapshot with unknown status strings', () => {
|
||||
const result = validate('onboarding_task_sources_snapshot', {
|
||||
github_status: 'signed-in',
|
||||
linear_status: 'not_connected',
|
||||
exit_action: 'continue'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_windows_terminal_snapshot with bounded choices', () => {
|
||||
const result = validate('onboarding_windows_terminal_snapshot', {
|
||||
default_shell: 'git_bash',
|
||||
right_click_behavior: 'menu',
|
||||
exit_action: 'continue',
|
||||
duration_ms: 1200,
|
||||
advanced_via: 'keyboard'
|
||||
})
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects onboarding_windows_terminal_snapshot with raw shell values', () => {
|
||||
const result = validate('onboarding_windows_terminal_snapshot', {
|
||||
default_shell: 'C:\\Program Files\\Git\\bin\\bash.exe',
|
||||
right_click_behavior: 'menu',
|
||||
exit_action: 'continue'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_started with cohort upgrade_backfill', () => {
|
||||
const result = validate('onboarding_started', { cohort: 'upgrade_backfill' })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects cohort on a non-onboarding event', () => {
|
||||
// Schema shape drives selective IPC cohort injection; reject it elsewhere.
|
||||
const result = validate('app_opened', {
|
||||
cohort: 'fresh_install'
|
||||
} as never)
|
||||
expect(result.ok).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts onboarding_started with cohort: undefined (classifier fail-soft)', () => {
|
||||
// Fail-soft IPC spread creates an explicit undefined cohort key; optional schema must accept it just like omission.
|
||||
const result = validate('onboarding_started', { cohort: undefined })
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
// First rejection warns; repeats in the same minute must not flood stderr.
|
||||
it('rate-limits warns to 1/min per event name', () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
_resetValidatorWarnCacheForTests()
|
||||
validate('app_opened', { bogus: true } as never)
|
||||
const afterFirst = warn.mock.calls.length
|
||||
validate('app_opened', { bogus2: true } as never)
|
||||
const afterSecond = warn.mock.calls.length
|
||||
expect(afterFirst).toBe(1)
|
||||
expect(afterSecond).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,6 @@ vi.mock('./client', () => ({
|
||||
import {
|
||||
collectWslProjects,
|
||||
countWindowsDriveReposWithWslWorktrees,
|
||||
listRunningDistrosForSnapshot,
|
||||
WSL_USAGE_SNAPSHOT_INTERVAL_MS,
|
||||
WslUsageReporter,
|
||||
type WslUsageStore
|
||||
@@ -74,14 +73,6 @@ describe('WslUsageReporter', () => {
|
||||
expect(gather).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('gathers nothing off Windows or while telemetry is off', async () => {
|
||||
expect(await reporter({ platform: 'darwin' }).reportIfDue()).toBe(false)
|
||||
expect(await reporter({ platform: 'linux' }).reportIfDue()).toBe(false)
|
||||
expect(await reporter({ telemetryEnabled: () => false }).reportIfDue()).toBe(false)
|
||||
expect(gather).not.toHaveBeenCalled()
|
||||
expect(trackMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not regather an hour after a failed gather', async () => {
|
||||
gather.mockRejectedValueOnce(new Error('store unavailable'))
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
@@ -111,40 +102,6 @@ describe('WslUsageReporter', () => {
|
||||
writeFileSync(join(dir, 'wsl-usage-telemetry.json'), 'not json')
|
||||
expect(await reporter().reportIfDue()).toBe(true)
|
||||
})
|
||||
|
||||
it('never throws when gathering fails', async () => {
|
||||
gather.mockRejectedValueOnce(new Error('reg.exe timed out'))
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
expect(await reporter().reportIfDue()).toBe(false)
|
||||
warn.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
describe('listRunningDistrosForSnapshot', () => {
|
||||
it('reuses a confirmed list without probing', async () => {
|
||||
const probe = vi.fn(async () => ['Other'])
|
||||
const running = await listRunningDistrosForSnapshot(
|
||||
[{ distro: 'Ubuntu' }],
|
||||
() => ['Ubuntu'],
|
||||
probe
|
||||
)
|
||||
expect(running).toEqual(['Ubuntu'])
|
||||
expect(probe).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('probes once when nothing is cached and a user distro exists', async () => {
|
||||
const probe = vi.fn(async () => ['Ubuntu'])
|
||||
const running = await listRunningDistrosForSnapshot([{ distro: 'Ubuntu' }], () => null, probe)
|
||||
expect(running).toEqual(['Ubuntu'])
|
||||
expect(probe).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('never probes without a user distro', async () => {
|
||||
const probe = vi.fn(async () => [])
|
||||
await listRunningDistrosForSnapshot([{ distro: 'docker-desktop' }], () => null, probe)
|
||||
await listRunningDistrosForSnapshot(null, () => null, probe)
|
||||
expect(probe).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('collectWslProjects', () => {
|
||||
|
||||
@@ -3,11 +3,8 @@ import { validate } from './validator'
|
||||
import {
|
||||
buildWslUsageSnapshot,
|
||||
classifyLocalWslProject,
|
||||
classifyWslHostProject,
|
||||
toWslMajorMinor,
|
||||
type WslUsageSnapshotInput
|
||||
} from './wsl-usage-snapshot'
|
||||
import { bucketWslUsageCount } from '../../shared/telemetry-wsl-usage-event-schemas'
|
||||
|
||||
const baseInput: WslUsageSnapshotInput = {
|
||||
registeredDistroCount: 0,
|
||||
@@ -19,33 +16,6 @@ const baseInput: WslUsageSnapshotInput = {
|
||||
wslHostsGateOpen: false
|
||||
}
|
||||
|
||||
describe('bucketWslUsageCount', () => {
|
||||
it.each([
|
||||
[0, '0'],
|
||||
[1, '1'],
|
||||
[2, '2-5'],
|
||||
[5, '2-5'],
|
||||
[6, '6-20'],
|
||||
[20, '6-20'],
|
||||
[21, '21+'],
|
||||
[500, '21+']
|
||||
])('%i -> %s', (count, bucket) => {
|
||||
expect(bucketWslUsageCount(count)).toBe(bucket)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toWslMajorMinor', () => {
|
||||
it('keeps major.minor only', () => {
|
||||
expect(toWslMajorMinor('2.4.13.0')).toBe('2.4')
|
||||
expect(toWslMajorMinor('2.6')).toBe('2.6')
|
||||
})
|
||||
it('drops anything not version-shaped', () => {
|
||||
expect(toWslMajorMinor(null)).toBeNull()
|
||||
expect(toWslMajorMinor('Ubuntu 2.4')).toBeNull()
|
||||
expect(toWslMajorMinor('2')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('classifyLocalWslProject', () => {
|
||||
it('counts a \\\\wsl$ share as inside the distro', () => {
|
||||
expect(
|
||||
@@ -109,15 +79,6 @@ describe('classifyLocalWslProject', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('classifyWslHostProject', () => {
|
||||
it('splits /mnt/<x> from distro paths', () => {
|
||||
expect(classifyWslHostProject({ path: '/mnt/c/src/app' }, 'Ubuntu').location).toBe(
|
||||
'windows_drive'
|
||||
)
|
||||
expect(classifyWslHostProject({ path: '/home/me/app' }, 'Ubuntu').location).toBe('distro_fs')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildWslUsageSnapshot', () => {
|
||||
it('reports buckets and booleans only, and passes the event validator', () => {
|
||||
const props = buildWslUsageSnapshot({
|
||||
@@ -167,11 +128,4 @@ describe('buildWslUsageSnapshot', () => {
|
||||
expect('wsl_version' in props).toBe(false)
|
||||
expect(validate('wsl_usage_snapshot', props).ok).toBe(true)
|
||||
})
|
||||
|
||||
it('the validator rejects identifying extras', () => {
|
||||
const props = buildWslUsageSnapshot(baseInput)
|
||||
expect(validate('wsl_usage_snapshot', { ...props, distro: 'Ubuntu' }).ok).toBe(false)
|
||||
expect(validate('wsl_usage_snapshot', { ...props, wsl_version: 'Ubuntu' }).ok).toBe(false)
|
||||
expect(validate('wsl_usage_snapshot', { ...props, distros_installed: '3' }).ok).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user