diff --git a/src/renderer/src/store/slices/repos-all-hosts.test.ts b/src/renderer/src/store/slices/repos-all-hosts.test.ts index 6a96f557a..f77e4db2b 100644 --- a/src/renderer/src/store/slices/repos-all-hosts.test.ts +++ b/src/renderer/src/store/slices/repos-all-hosts.test.ts @@ -479,7 +479,9 @@ describe('fetchReposForAllHosts', () => { await store.getState().fetchReposForAllHosts() expectSharedProjectMetadata(store.getState().projects, sharedProjectId) - expect(store.getState().projectHostSetups).toEqual( + const setups = store.getState().projectHostSetups + expect(setups).toHaveLength(2) + expect(setups).toEqual( expect.arrayContaining([ expect.objectContaining({ projectId: sharedProjectId, diff --git a/src/renderer/src/store/slices/repos-nested-ssh-projection.test.ts b/src/renderer/src/store/slices/repos-nested-ssh-projection.test.ts index 67eb2d1c2..bcdc36afd 100644 --- a/src/renderer/src/store/slices/repos-nested-ssh-projection.test.ts +++ b/src/renderer/src/store/slices/repos-nested-ssh-projection.test.ts @@ -74,7 +74,9 @@ it('preserves distinct SSH execution setups behind the same runtime owner', asyn await store.getState().fetchRepos() - expect(store.getState().projectHostSetups).toEqual( + const setups = store.getState().projectHostSetups + expect(setups).toHaveLength(2) + expect(setups).toEqual( expect.arrayContaining([ expect.objectContaining({ id: 'direct-setup', @@ -91,3 +93,67 @@ it('preserves distinct SSH execution setups behind the same runtime owner', asyn ]) ) }) + +it('prefers an authoritative paired-runtime setup over its repo-derived fallback', async () => { + const project: Project = { + id: `repo:${remoteRepo.id}`, + displayName: remoteRepo.displayName, + badgeColor: remoteRepo.badgeColor, + sourceRepoIds: [remoteRepo.id], + createdAt: 1, + updatedAt: 1 + } + const setup: ProjectHostSetup = { + id: remoteRepo.id, + projectId: project.id, + hostId: 'local', + repoId: remoteRepo.id, + path: remoteRepo.path, + displayName: remoteRepo.displayName, + setupState: 'ready', + setupMethod: 'legacy-repo', + createdAt: 1, + updatedAt: 1 + } + runtimeEnvironmentCall.mockImplementation(({ method }: { method: string }) => { + if (method === 'repo.list') { + return Promise.resolve({ + id: 'repo-list', + ok: true, + result: { repos: [remoteRepo] }, + _meta: { runtimeId: 'runtime-remote' } + }) + } + if (method === 'project.list') { + return Promise.resolve({ + id: 'project-list', + ok: true, + result: { projects: [project] }, + _meta: { runtimeId: 'runtime-remote' } + }) + } + if (method === 'projectHostSetup.list') { + return Promise.resolve({ + id: 'setup-list', + ok: true, + result: { setups: [setup] }, + _meta: { runtimeId: 'runtime-remote' } + }) + } + throw new Error(`Unexpected method ${method}`) + }) + const store = createTestStore() + store.setState({ settings: { activeRuntimeEnvironmentId: 'env-1' } as never }) + + await store.getState().fetchRepos() + + expect(store.getState().projectHostSetups).toEqual([ + { + ...setup, + hostId: 'runtime:env-1', + executionHostId: 'runtime:env-1', + runtimeOwnerEnvironmentId: 'env-1', + connectionId: null + } + ]) +}) diff --git a/src/renderer/src/store/slices/repos.ts b/src/renderer/src/store/slices/repos.ts index 4d3083e9e..b828c45f4 100644 --- a/src/renderer/src/store/slices/repos.ts +++ b/src/renderer/src/store/slices/repos.ts @@ -613,9 +613,9 @@ function mergeProjectHostSetupCompatibility( derived: Pick, fetched: ProjectHostSetupProjection ): Pick { - const fetchedSetupOwners = new Set(fetched.setups.map(getProjectHostSetupOwnerKey)) + const fetchedRepoSetupKeys = new Set(fetched.setups.map(getRepoDerivedSetupKey)) const derivedSetups = derived.projectHostSetups.filter( - (setup) => !fetchedSetupOwners.has(getProjectHostSetupOwnerKey(setup)) + (setup) => !fetchedRepoSetupKeys.has(getRepoDerivedSetupKey(setup)) ) const projectHostSetups = mergeProjectHostSetupsByOwner(derivedSetups, fetched.setups) const setupProjectIds = new Set(projectHostSetups.map((setup) => setup.projectId)) @@ -628,6 +628,11 @@ function mergeProjectHostSetupCompatibility( } } +function getRepoDerivedSetupKey(setup: ProjectHostSetup): string { + // Why: authoritative routing provenance may be absent from the repo-derived fallback it replaces. + return JSON.stringify([setup.hostId, setup.repoId || setup.id]) +} + function getProjectHostSetupOwnerKey(setup: ProjectHostSetup): string { return JSON.stringify([ setup.hostId,