mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 03:57:35 +00:00
* WIP: same-host same-remote checkouts keep their own project id * WIP: claim the shared project id per checkout surface; drop the display-only clone split * WIP: index derived ids by repo in the hydration normalizer * Resolve workspace project affiliation from current checkout owners * fix: preserve concrete project ownership across catalogs and operations * fix: combine recent project selector imports * fix: enforce canonical catalog reads and preserve search continuity * fix: scope catalog lint exceptions to renderer files * Resolve project search ownership before native candidate limits * Combine duplicate session scope imports * fix: derive returned history row ownership on the host * Reconcile full session history replies by content * Require authoritative project catalogs for terminal snapshots * Preserve registered project claims and partial history results * test: check orphaned setup cleanup state without assertions * fix: align catalog callers and native search fixture contracts * test: check full repo shapes in runtime routing fixtures * Fix checked catalog inputs in compiler regression fixtures * Keep checked PTY fixture repos mutable * test: align catalog fixtures with canonical readers * test: check catalog and runtime fixture inputs * test: check catalog fixtures without outer assertions * test: follow persistence harness relocation * Adapt catalog test inputs to integrated main checks * Test History hashing across cached depth revalidation * test(settings): expect host-qualified setup removal * test: capture host split listings and scope relay recovery * ci: regenerate merge ref * test: type host-split listing diagnostics
222 lines
8.6 KiB
TypeScript
222 lines
8.6 KiB
TypeScript
import { execFileSync } from 'node:child_process'
|
|
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { mkdtemp } from 'node:fs/promises'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import type { Page } from '@stablyai/playwright-test'
|
|
import { selectCanonicalProjects } from '../../src/renderer/src/store/project-host-setup-selector'
|
|
import { test, expect } from './helpers/orca-app'
|
|
import { waitForSessionReady } from './helpers/store'
|
|
|
|
const tempRoots: string[] = []
|
|
const CLEANUP_RETRY_COUNT = 6
|
|
|
|
async function removeTempRoot(root: string): Promise<void> {
|
|
for (let attempt = 0; attempt < CLEANUP_RETRY_COUNT; attempt += 1) {
|
|
try {
|
|
rmSync(root, { recursive: true, force: true })
|
|
return
|
|
} catch (error) {
|
|
if (attempt === CLEANUP_RETRY_COUNT - 1) {
|
|
throw error
|
|
}
|
|
// Why: Windows and WSL-backed git probes can release repo handles shortly
|
|
// after the Electron fixture closes; retry to keep the smoke idempotent.
|
|
await new Promise((resolve) => setTimeout(resolve, 250 * (attempt + 1)))
|
|
}
|
|
}
|
|
}
|
|
|
|
async function createGitRepo(prefix: string, repoName: string): Promise<string> {
|
|
const rootPath = await mkdtemp(path.join(os.tmpdir(), prefix))
|
|
tempRoots.push(rootPath)
|
|
const repoPath = path.join(rootPath, repoName)
|
|
mkdirSync(repoPath, { recursive: true })
|
|
execFileSync('git', ['init'], { cwd: repoPath, stdio: 'pipe' })
|
|
execFileSync('git', ['config', 'user.email', 'e2e@test.local'], {
|
|
cwd: repoPath,
|
|
stdio: 'pipe'
|
|
})
|
|
execFileSync('git', ['config', 'user.name', 'E2E Test'], { cwd: repoPath, stdio: 'pipe' })
|
|
writeFileSync(path.join(repoPath, 'README.md'), `# ${repoName}\n`)
|
|
execFileSync('git', ['add', 'README.md'], { cwd: repoPath, stdio: 'pipe' })
|
|
execFileSync('git', ['commit', '-m', 'Initial commit'], { cwd: repoPath, stdio: 'pipe' })
|
|
execFileSync('git', ['branch', '-M', 'main'], { cwd: repoPath, stdio: 'pipe' })
|
|
return repoPath
|
|
}
|
|
|
|
async function openRepoSettings(page: Page, repoId: string): Promise<void> {
|
|
await page.evaluate((nextRepoId) => {
|
|
const state = window.__store!.getState()
|
|
state.setSettingsSearchQuery('')
|
|
state.openSettingsTarget({ pane: 'repo', repoId: nextRepoId })
|
|
state.openSettingsPage()
|
|
}, repoId)
|
|
await expect(page.getByPlaceholder('Search settings')).toBeVisible({ timeout: 10_000 })
|
|
const maybeLaterButton = page.getByRole('button', { name: 'Maybe Later' })
|
|
if (await maybeLaterButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
|
|
await maybeLaterButton.click()
|
|
}
|
|
}
|
|
|
|
async function chooseProjectRuntime(
|
|
page: Page,
|
|
repoId: string,
|
|
runtimeLabel: 'Windows' | 'WSL'
|
|
): Promise<void> {
|
|
const section = page.locator(`[data-settings-section="repo-${repoId}"]`)
|
|
await section.getByRole('radio', { name: runtimeLabel, exact: true }).click()
|
|
const applyButton = section.getByRole('button', { name: 'Apply runtime change' })
|
|
if (await applyButton.isVisible({ timeout: 1_000 }).catch(() => false)) {
|
|
await applyButton.click()
|
|
}
|
|
}
|
|
|
|
test.afterAll(async () => {
|
|
for (const root of tempRoots.splice(0)) {
|
|
await removeTempRoot(root)
|
|
}
|
|
})
|
|
|
|
test.describe('Windows project runtime smoke', () => {
|
|
test('keeps a Windows-host project and WSL project available side by side', async ({
|
|
orcaPage,
|
|
testRepoPath
|
|
}) => {
|
|
test.skip(process.platform !== 'win32', 'Windows project runtime smoke requires Windows')
|
|
await waitForSessionReady(orcaPage)
|
|
|
|
const wsl = await orcaPage.evaluate(async () => ({
|
|
available: await window.api.wsl.isAvailable(),
|
|
distros: await window.api.wsl.listDistros()
|
|
}))
|
|
test.skip(!wsl.available || wsl.distros.length === 0, 'WSL distro is required for smoke')
|
|
const wslDistro = wsl.distros[0]!
|
|
const wslRepoPath = await createGitRepo('orca-e2e-project-runtime-', 'wsl-runtime-project')
|
|
|
|
const catalog = await orcaPage.evaluate(
|
|
async ({ hostRepoPath, wslRepoPath }) => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
|
|
await window.api.repos.add({ path: wslRepoPath })
|
|
await store.getState().fetchRepos({ runtimeEnvironmentId: null, throwOnError: true })
|
|
const state = store.getState()
|
|
const hostRepo = state.repos.find((repo) => repo.path === hostRepoPath)
|
|
const wslRepo = state.repos.find((repo) => repo.path === wslRepoPath)
|
|
if (!hostRepo || !wslRepo) {
|
|
throw new Error('Expected host and WSL smoke repos to be loaded')
|
|
}
|
|
|
|
return {
|
|
repos: state.repos,
|
|
endpointProjects: state.endpointProjects,
|
|
endpointProjectHostSetups: state.endpointProjectHostSetups,
|
|
hostRepo,
|
|
wslRepo
|
|
}
|
|
},
|
|
{ hostRepoPath: testRepoPath, wslRepoPath }
|
|
)
|
|
const projects = selectCanonicalProjects(catalog)
|
|
const hostProject = projects.find((project) =>
|
|
project.sourceRepoIds.includes(catalog.hostRepo.id)
|
|
)
|
|
const wslProject = projects.find((project) =>
|
|
project.sourceRepoIds.includes(catalog.wslRepo.id)
|
|
)
|
|
if (!hostProject || !wslProject) {
|
|
throw new Error('Expected host and WSL smoke projects to be loaded')
|
|
}
|
|
const smoke = await orcaPage.evaluate(
|
|
async ({ hostProjectId, wslProjectId, hostRepoId, wslRepoId, wslDistro }) => {
|
|
const state = window.__store!.getState()
|
|
await state.updateProject(
|
|
hostProjectId,
|
|
{
|
|
localWindowsRuntimePreference: { kind: 'windows-host' }
|
|
},
|
|
'local'
|
|
)
|
|
await state.updateProject(
|
|
wslProjectId,
|
|
{
|
|
localWindowsRuntimePreference: { kind: 'wsl', distro: wslDistro }
|
|
},
|
|
'local'
|
|
)
|
|
|
|
const hostWorktrees = await window.api.worktrees.listDetected({ repoId: hostRepoId })
|
|
const wslWorktrees = await window.api.worktrees.listDetected({ repoId: wslRepoId })
|
|
return {
|
|
hostRepoId,
|
|
wslRepoId,
|
|
wslDistro,
|
|
hostWorktreeCount: hostWorktrees.worktrees.length,
|
|
wslWorktreeCount: wslWorktrees.worktrees.length
|
|
}
|
|
},
|
|
{
|
|
hostProjectId: hostProject.id,
|
|
wslProjectId: wslProject.id,
|
|
hostRepoId: catalog.hostRepo.id,
|
|
wslRepoId: catalog.wslRepo.id,
|
|
wslDistro
|
|
}
|
|
)
|
|
|
|
expect(smoke.hostWorktreeCount).toBeGreaterThan(0)
|
|
expect(smoke.wslWorktreeCount).toBeGreaterThan(0)
|
|
|
|
await openRepoSettings(orcaPage, smoke.hostRepoId)
|
|
const hostSection = orcaPage.locator(`[data-settings-section="repo-${smoke.hostRepoId}"]`)
|
|
await expect(hostSection.getByText('Project Runtime')).toBeVisible()
|
|
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
|
|
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'WSL')
|
|
await expect(
|
|
hostSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
|
|
).toBeVisible()
|
|
const afterWslCatalog = await orcaPage.evaluate(() => {
|
|
const state = window.__store!.getState()
|
|
return {
|
|
repos: state.repos,
|
|
endpointProjects: state.endpointProjects,
|
|
endpointProjectHostSetups: state.endpointProjectHostSetups
|
|
}
|
|
})
|
|
const hostAfterWslUiSwitch = selectCanonicalProjects(afterWslCatalog).find((project) =>
|
|
project.sourceRepoIds.includes(smoke.hostRepoId)
|
|
)?.localWindowsRuntimePreference
|
|
expect(hostAfterWslUiSwitch).toEqual({ kind: 'wsl', distro: smoke.wslDistro })
|
|
|
|
await chooseProjectRuntime(orcaPage, smoke.hostRepoId, 'Windows')
|
|
await expect(hostSection.getByText('This project runs on Windows.')).toBeVisible()
|
|
const afterWindowsCatalog = await orcaPage.evaluate(() => {
|
|
const state = window.__store!.getState()
|
|
return {
|
|
repos: state.repos,
|
|
endpointProjects: state.endpointProjects,
|
|
endpointProjectHostSetups: state.endpointProjectHostSetups
|
|
}
|
|
})
|
|
const hostAfterWindowsUiSwitch = selectCanonicalProjects(afterWindowsCatalog).find((project) =>
|
|
project.sourceRepoIds.includes(smoke.hostRepoId)
|
|
)?.localWindowsRuntimePreference
|
|
expect(hostAfterWindowsUiSwitch).toEqual({ kind: 'windows-host' })
|
|
|
|
await openRepoSettings(orcaPage, smoke.wslRepoId)
|
|
const wslSection = orcaPage.locator(`[data-settings-section="repo-${smoke.wslRepoId}"]`)
|
|
await expect(wslSection.getByText('Project Runtime')).toBeVisible()
|
|
await expect(
|
|
wslSection.getByText(`This project runs in ${smoke.wslDistro} via WSL.`)
|
|
).toBeVisible()
|
|
|
|
await orcaPage.evaluate(async (repoId) => {
|
|
const repo = window.__store!.getState().repos.find((entry) => entry.id === repoId)
|
|
await window.api.repos.removeForHost({ repoId, hostId: repo?.executionHostId ?? 'local' })
|
|
}, smoke.wslRepoId)
|
|
})
|
|
})
|