Files
windmill/frontend/src/lib/remoteDeploy.test.ts
T
Ruben FiszelandClaude Opus 5 4d12ea4614 feat: deploy from the UI to a workspace on another instance (#11245)
* feat: deploy from the UI to a workspace on another instance

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the remote deploy proxy from being spent by a link

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: key remote deploy proxy URLs instead of a global client header

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep the remote deploy proxy key out of logs and restricted hands

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: serialize remote deploy connect with account and key changes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: key remote deploy tokens to the account and connect by signing in

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: bind remote deploy connect to its target and order its locks

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: serialize remote deploy connect with target changes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: list every lock remote deploy connect takes in auth-surface

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: never wait on the membership lock in remote deploy connect

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep a superseded target response out of the settings form

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: void stale remote deploy tokens on read instead of locking in connect

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: void remote deploy tokens older than the last target change

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: order remote deploy connections by when their connect started

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: bind stored remote deploy tokens to the membership and target they were connected under

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: answer remote deploy connect without settings as no target

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 01:40:53 +02:00

45 lines
1.6 KiB
TypeScript

import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { PENDING_TTL_MS, remoteDeployAuthorizeUrl, takePendingConnect } from './remoteDeploy'
const target = { base_url: 'https://prod.example.com', workspace_id: 'prod' }
function startConnect(workspace = 'dev'): string {
const url = new URL(remoteDeployAuthorizeUrl(target, workspace, '/deploy/flow/f/a'))
return url.searchParams.get('state')!
}
describe('remote deploy connect state', () => {
beforeEach(() => {
localStorage.clear()
vi.stubGlobal('window', { location: { origin: 'https://dev.example.com' } })
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
})
// The state is what keeps any other page from planting a token as the user's.
it('matches only the state it issued, and only once', () => {
const state = startConnect()
expect(takePendingConnect('forged')).toBeUndefined()
expect(takePendingConnect(state)).toMatchObject({ workspace: 'dev', target })
expect(takePendingConnect(state)).toBeUndefined()
})
it('keeps concurrent attempts apart', () => {
const first = startConnect('dev')
const second = startConnect('staging')
expect(takePendingConnect(first)?.workspace).toBe('dev')
expect(takePendingConnect(second)?.workspace).toBe('staging')
})
it('expires an attempt left unfinished, and drops it from storage', () => {
vi.useFakeTimers()
const abandoned = startConnect()
vi.advanceTimersByTime(PENDING_TTL_MS + 60_000)
startConnect()
expect(localStorage.length).toBe(1)
expect(takePendingConnect(abandoned)).toBeUndefined()
})
})