Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,003 changes: 910 additions & 93 deletions apps/docs/content/docs/integrations/ashby.mdx

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions apps/sim/blocks/blocks/ashby.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ describe('AshbyBlock', () => {
expect(result.socialLinks).toEqual([{ type: 'Twitter', url: 'https://twitter.com/jane' }])
})

it('preserves an empty JSON array so users can clear all social links', () => {
const result = AshbyBlock.tools.config.params!(
buildParams('update_candidate', { socialLinks: '[]' })
)
expect(result.socialLinks).toEqual([])
})

it('throws instead of silently dropping the field when the JSON is malformed', () => {
// A silent [] here would let the Ashby update proceed without applying
// the requested links and with no error shown to the workflow author.
Expand All @@ -66,6 +73,43 @@ describe('AshbyBlock', () => {
})
})

describe('nullable candidate updates', () => {
it('preserves nulls from dynamic references for aliased update fields', () => {
const result = AshbyBlock.tools.config.params!(
buildParams('update_candidate', {
updateName: null,
candidateLocation: null,
candidateCreatedAt: null,
})
)
expect(result.name).toBeNull()
expect(result.location).toBeNull()
expect(result.createdAt).toBeNull()
})
})

describe('archiveEmail parsing (change_application_stage)', () => {
it('parses the documented archive email object', () => {
const result = AshbyBlock.tools.config.params!(
buildParams('change_application_stage', {
archiveEmail: '{"communicationTemplateId":"template-1","sendAt":"2026-09-02T16:32:00Z"}',
})
)
expect(result.archiveEmail).toEqual({
communicationTemplateId: 'template-1',
sendAt: '2026-09-02T16:32:00Z',
})
})

it('rejects non-object archive email input', () => {
expect(() =>
AshbyBlock.tools.config.params!(
buildParams('change_application_stage', { archiveEmail: 'true' })
)
).toThrow(/expected a JSON object/)
})
})

describe('wandConfig on array-shaped fields', () => {
it('does not request object-wrapped output for alternateEmailAddresses or socialLinks', () => {
// generationType 'json-object' makes the wand API append "the response
Expand Down Expand Up @@ -324,6 +368,57 @@ describe('AshbyBlock', () => {
const condition = syncToken?.condition as { value: string[] }
expect(condition.value).toContain('list_jobs')
})

it('maps the editor status selection to the tool array contract', () => {
const result = AshbyBlock.tools.config.params!(
buildParams('list_jobs', { jobStatus: 'Open' })
)
expect(result.status).toEqual(['Open'])
})

it('maps the shared draft-posting switch to the endpoint-specific parameter', () => {
const listParams = AshbyBlock.tools.config.params!(
buildParams('list_jobs', { includeUnpublishedJobPostingIds: true })
)
expect(listParams.includeUnpublishedJobPostingsIds).toBe(true)
expect(listParams.includeUnpublishedJobPostingIds).toBeUndefined()

const infoParams = AshbyBlock.tools.config.params!(
buildParams('get_job', { includeUnpublishedJobPostingIds: true })
)
expect(infoParams.includeUnpublishedJobPostingIds).toBe(true)
expect(infoParams.includeUnpublishedJobPostingsIds).toBeUndefined()
})
})

describe('expanded list controls', () => {
it('exposes only provider-supported pagination and sync controls', () => {
const cursor = AshbyBlock.subBlocks.find((s) => s.id === 'cursor')
const cursorOperations = (cursor?.condition as { value: string[] }).value
expect(cursorOperations).not.toContain('search_candidates')
expect(cursorOperations).not.toContain('search_jobs')

const syncToken = AshbyBlock.subBlocks.find((s) => s.id === 'syncToken')
const syncOperations = (syncToken?.condition as { value: string[] }).value
expect(syncOperations).toContain('list_application_feedback')
})

it('exposes archived interview plans in the editor', () => {
const includeArchived = AshbyBlock.subBlocks.find((s) => s.id === 'includeArchived')
const operations = (includeArchived?.condition as { value: string[] }).value
expect(operations).toContain('list_interview_plans')
})
})

describe('alternate lookup identifiers', () => {
it('does not require the Ashby UUID when an alternate lookup is supported', () => {
const candidateId = AshbyBlock.subBlocks.find((s) => s.id === 'candidateId')
const applicationId = AshbyBlock.subBlocks.find((s) => s.id === 'applicationId')
const candidateRequired = candidateId?.required as { value?: string[] }
const applicationRequired = applicationId?.required as { value?: string[] }
expect(candidateRequired.value).not.toContain('get_candidate')
expect(applicationRequired.value).not.toContain('get_application')
})
})

describe('list_applications candidateId filter', () => {
Expand Down
Loading
Loading