mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
2b525d28db
* fix: run on-behalf-of scripts under their own identity from workflow steps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: correct the on-behalf-of provenance comment in the script draft deploy Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin preserve_on_behalf_of forwarding in the script and flow draft deploys Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: drop the historical comparison from the draft-deploy test comment Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
127 lines
5.0 KiB
TypeScript
127 lines
5.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { draftBaseIsStale, deployDraft } from './utils_draft_deploy'
|
|
|
|
vi.mock('$lib/gen', () => ({
|
|
ScriptService: { getScriptByPath: vi.fn(), createScript: vi.fn() },
|
|
FlowService: { getFlowByPath: vi.fn(), createFlow: vi.fn(), updateFlow: vi.fn() },
|
|
DraftService: { deleteDraft: vi.fn() },
|
|
AppService: {},
|
|
VariableService: {},
|
|
ResourceService: {},
|
|
ScheduleService: {},
|
|
HttpTriggerService: {},
|
|
WebsocketTriggerService: {},
|
|
PostgresTriggerService: {},
|
|
KafkaTriggerService: {},
|
|
NatsTriggerService: {},
|
|
MqttTriggerService: {},
|
|
AmqpTriggerService: {},
|
|
SqsTriggerService: {},
|
|
GcpTriggerService: {},
|
|
AzureTriggerService: {},
|
|
EmailTriggerService: {}
|
|
}))
|
|
vi.mock('$lib/userDraftDbSyncer.svelte', () => ({ UserDraftDbSyncer: { save: vi.fn() } }))
|
|
vi.mock('$lib/workspaceDrafts.svelte', () => ({ invalidateWorkspaceDrafts: vi.fn() }))
|
|
vi.mock('$lib/workspaceComparison', () => ({ invalidateWorkspaceComparison: vi.fn() }))
|
|
vi.mock('$lib/localDraftHints.svelte', () => ({ setLocalDraftHint: vi.fn() }))
|
|
vi.mock('$lib/rawAppDeploy', () => ({ deployRawAppDraft: vi.fn() }))
|
|
vi.mock('$lib/components/raw_apps/utils', () => ({ canonicalRawAppDiffValue: vi.fn() }))
|
|
vi.mock('$lib/appDiffSides', () => ({ classicAppDraftParts: vi.fn() }))
|
|
vi.mock('$lib/utils_deployable', () => ({ TRIGGER_RUNTIME_IGNORE: [] }))
|
|
|
|
import { ScriptService, FlowService } from '$lib/gen'
|
|
|
|
// draftBaseIsStale compares a draft's base pointer against the deployed head
|
|
// of the item it was fetched with (`get_draft=true`). Shared by CompareDrafts
|
|
// and the session Edits drawer — a regression here silently hides (or
|
|
// fabricates) the "started from an older deployed version" warning.
|
|
|
|
describe('draftBaseIsStale', () => {
|
|
it('script: stale iff the draft parent_hash differs from the deployed hash', () => {
|
|
expect(draftBaseIsStale('script', { hash: 'v2', draft: { parent_hash: 'v1' } })).toBe(true)
|
|
expect(draftBaseIsStale('script', { hash: 'v2', draft: { parent_hash: 'v2' } })).toBe(false)
|
|
})
|
|
|
|
it('script: no base pointer or no head → not stale (nothing to compare)', () => {
|
|
expect(draftBaseIsStale('script', { hash: 'v2', draft: {} })).toBe(false)
|
|
expect(draftBaseIsStale('script', { draft: { parent_hash: 'v1' } })).toBe(false)
|
|
})
|
|
|
|
it('flow: compares the pinned version_id against the deployed head', () => {
|
|
expect(draftBaseIsStale('flow', { version_id: 7, draft: { version_id: 5 } })).toBe(true)
|
|
expect(draftBaseIsStale('flow', { version_id: 7, draft: { version_id: 7 } })).toBe(false)
|
|
expect(draftBaseIsStale('flow', { version_id: 7, draft: {} })).toBe(false)
|
|
})
|
|
|
|
it('app/raw_app: compares parent_version against the last of versions', () => {
|
|
expect(draftBaseIsStale('app', { versions: [1, 2, 3], draft: { parent_version: 2 } })).toBe(
|
|
true
|
|
)
|
|
expect(draftBaseIsStale('raw_app', { versions: [1, 2, 3], draft: { parent_version: 3 } })).toBe(
|
|
false
|
|
)
|
|
expect(draftBaseIsStale('app', { versions: [], draft: { parent_version: 2 } })).toBe(false)
|
|
})
|
|
|
|
it('no draft on the response → not stale', () => {
|
|
expect(draftBaseIsStale('script', { hash: 'v2' })).toBe(false)
|
|
expect(draftBaseIsStale('script', undefined)).toBe(false)
|
|
})
|
|
})
|
|
|
|
// Without preserve_on_behalf_of the backend rewrites on_behalf_of_email to the
|
|
// deploying user, so deploying a draft silently re-points the runnable's
|
|
// identity.
|
|
|
|
describe('deployDraft preserves on_behalf_of', () => {
|
|
beforeEach(() => vi.clearAllMocks())
|
|
|
|
it('script: forwards the flag when the draft carries an on_behalf_of_email', async () => {
|
|
vi.mocked(ScriptService.getScriptByPath).mockResolvedValueOnce({
|
|
hash: 'v1',
|
|
draft: { path: 'f/admin/send_email', on_behalf_of_email: 'alice@windmill.dev' }
|
|
} as any)
|
|
|
|
expect(await deployDraft('script', 'f/admin/send_email', 'ws')).toEqual({ success: true })
|
|
expect(ScriptService.createScript).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
requestBody: expect.objectContaining({
|
|
on_behalf_of_email: 'alice@windmill.dev',
|
|
preserve_on_behalf_of: true
|
|
})
|
|
})
|
|
)
|
|
})
|
|
|
|
it('script: omits the flag when the draft has no on_behalf_of_email', async () => {
|
|
vi.mocked(ScriptService.getScriptByPath).mockResolvedValueOnce({
|
|
hash: 'v1',
|
|
draft: { path: 'f/admin/send_email' }
|
|
} as any)
|
|
|
|
await deployDraft('script', 'f/admin/send_email', 'ws')
|
|
expect(ScriptService.createScript).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
requestBody: expect.objectContaining({ preserve_on_behalf_of: undefined })
|
|
})
|
|
)
|
|
})
|
|
|
|
it('flow: forwards the flag when the draft carries an on_behalf_of_email', async () => {
|
|
vi.mocked(FlowService.getFlowByPath).mockResolvedValueOnce({
|
|
draft: { path: 'f/admin/notify', value: {}, on_behalf_of_email: 'alice@windmill.dev' }
|
|
} as any)
|
|
|
|
expect(await deployDraft('flow', 'f/admin/notify', 'ws')).toEqual({ success: true })
|
|
expect(FlowService.updateFlow).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
requestBody: expect.objectContaining({
|
|
on_behalf_of_email: 'alice@windmill.dev',
|
|
preserve_on_behalf_of: true
|
|
})
|
|
})
|
|
)
|
|
})
|
|
})
|