mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 06:10:32 +00:00
*PR Created by the Glary-Bot Agent*
---
## Summary
The careers page at comfy.org/careers was linking every role to its
Ashby application form (`.../{id}/application`) instead of the job
description page (`.../{id}/`). Users expect to first read the role
description, not land on the submit-resume page.
Ashby's job board API returns both `jobUrl` (description) and `applyUrl`
(application form). `toDomainRole` was preferring `applyUrl`; this PR
switches to `jobUrl` and renames the `Role` field accordingly so the
field name matches its meaning.
## Changes
- `apps/website/src/utils/ashby.ts`: use `job.jobUrl` directly instead
of `job.applyUrl ?? job.jobUrl`.
- `apps/website/src/data/roles.ts`: rename `Role.applyUrl` →
`Role.jobUrl`.
- `apps/website/src/components/careers/RolesSection.vue`: update the `<a
:href>` binding.
- `apps/website/src/data/ashby-roles.snapshot.json`: regenerated
fallback snapshot — URLs stripped of `/application`, `id`s recomputed
from the new URLs.
- Unit + E2E tests updated; new E2E assertion that links do not end in
`/application` prevents regressions.
The Ashby schema (`ashby.schema.ts`) still accepts `applyUrl` since the
API returns it — we just no longer consume it.
## Verification
- `pnpm test:unit` — 70/70 pass
- `pnpm typecheck` — 0 errors
- `pnpm build` — succeeds; inspected `dist/careers/index.html`, all 19
Ashby links now point to description URLs and zero contain
`/application`
- Oracle code review — 0 issues
Fixes user report in #hiring-ideas (Slack).
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12200-fix-website-link-careers-page-to-Ashby-job-description-not-application-form-35e6d73d3650815cbedadf974f7d3364)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
310 lines
10 KiB
TypeScript
310 lines
10 KiB
TypeScript
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import type { AshbyJobPosting } from './ashby.schema'
|
|
import type { RolesSnapshot } from '../data/roles'
|
|
|
|
import { fetchRolesForBuild, resetAshbyFetcherForTests } from './ashby'
|
|
|
|
const BASE_URL = 'https://ashby.test'
|
|
const BOARD = 'comfy-org'
|
|
const KEY = 'abc-123-secret'
|
|
|
|
function validJob(overrides: Partial<AshbyJobPosting> = {}): unknown {
|
|
return {
|
|
title: 'Design Engineer',
|
|
department: 'Engineering',
|
|
location: 'San Francisco',
|
|
isListed: true,
|
|
jobUrl: 'https://jobs.ashbyhq.com/comfy-org/design-engineer',
|
|
applyUrl: 'https://jobs.ashbyhq.com/comfy-org/design-engineer/apply',
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
function response(body: unknown, init: Partial<ResponseInit> = {}): Response {
|
|
const base: ResponseInit = {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' }
|
|
}
|
|
return new Response(JSON.stringify(body), { ...base, ...init })
|
|
}
|
|
|
|
function makeSnapshot(roleCount = 2): RolesSnapshot {
|
|
const roles = Array.from({ length: roleCount }, (_, i) => ({
|
|
id: `snapshot-role-${i}`,
|
|
title: `Snapshot Role ${i}`,
|
|
department: 'Engineering',
|
|
location: 'San Francisco',
|
|
jobUrl: `https://jobs.ashbyhq.com/comfy-org/snapshot-${i}`
|
|
}))
|
|
return {
|
|
fetchedAt: '2026-04-01T00:00:00.000Z',
|
|
departments: [{ name: 'ENGINEERING', key: 'engineering', roles }]
|
|
}
|
|
}
|
|
|
|
function withSnapshotDir(snapshot: RolesSnapshot | null): URL {
|
|
const dir = mkdtempSync(join(tmpdir(), 'ashby-test-'))
|
|
const file = join(dir, 'ashby-roles.snapshot.json')
|
|
if (snapshot) writeFileSync(file, JSON.stringify(snapshot))
|
|
return pathToFileURL(file)
|
|
}
|
|
|
|
describe('fetchRolesForBuild', () => {
|
|
const savedApiKey = process.env.WEBSITE_ASHBY_API_KEY
|
|
const savedBoardName = process.env.WEBSITE_ASHBY_JOB_BOARD_NAME
|
|
|
|
beforeEach(() => {
|
|
resetAshbyFetcherForTests()
|
|
delete process.env.WEBSITE_ASHBY_API_KEY
|
|
delete process.env.WEBSITE_ASHBY_JOB_BOARD_NAME
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
process.env.WEBSITE_ASHBY_API_KEY = savedApiKey
|
|
process.env.WEBSITE_ASHBY_JOB_BOARD_NAME = savedBoardName
|
|
})
|
|
|
|
it('returns fresh when the API succeeds', async () => {
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({ apiVersion: '1', jobs: [validJob()] })
|
|
)
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('fresh')
|
|
if (outcome.status !== 'fresh') return
|
|
expect(outcome.droppedCount).toBe(0)
|
|
expect(outcome.snapshot.departments).toHaveLength(1)
|
|
expect(outcome.snapshot.departments[0]!.roles[0]!.jobUrl).toBe(
|
|
'https://jobs.ashbyhq.com/comfy-org/design-engineer'
|
|
)
|
|
})
|
|
|
|
it('drops invalid roles individually and keeps the valid ones', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({
|
|
apiVersion: '1',
|
|
jobs: [validJob(), validJob({ title: 'Bad Role', jobUrl: 'not-a-url' })]
|
|
})
|
|
)
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('fresh')
|
|
if (outcome.status !== 'fresh') return
|
|
expect(outcome.droppedCount).toBe(1)
|
|
expect(outcome.droppedRoles[0]!.title).toBe('Bad Role')
|
|
expect(outcome.snapshot.departments[0]!.roles).toHaveLength(1)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('renders an empty-but-fresh outcome when hiring is paused', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () => response({ apiVersion: '1', jobs: [] }))
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('fresh')
|
|
if (outcome.status !== 'fresh') return
|
|
expect(outcome.snapshot.departments).toEqual([])
|
|
expect(outcome.droppedCount).toBe(0)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('normalizes missing department and location to safe defaults', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const job = validJob()
|
|
delete (job as Record<string, unknown>).department
|
|
delete (job as Record<string, unknown>).location
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({ apiVersion: '1', jobs: [job] })
|
|
)
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('fresh')
|
|
if (outcome.status !== 'fresh') return
|
|
const [department] = outcome.snapshot.departments
|
|
expect(department?.name).toBe('OTHER')
|
|
expect(department?.roles[0]?.location).toBe('Remote')
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('filters out roles with isListed=false', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({
|
|
apiVersion: '1',
|
|
jobs: [validJob(), validJob({ title: 'Hidden', isListed: false })]
|
|
})
|
|
)
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('fresh')
|
|
if (outcome.status !== 'fresh') return
|
|
const titles = outcome.snapshot.departments.flatMap((d) =>
|
|
d.roles.map((r) => r.title)
|
|
)
|
|
expect(titles).not.toContain('Hidden')
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('returns stale with missing env when the snapshot is present', async () => {
|
|
const snapshot = makeSnapshot()
|
|
const snapshotUrl = withSnapshotDir(snapshot)
|
|
const fetchImpl = vi.fn()
|
|
const outcome = await fetchRolesForBuild({
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('stale')
|
|
if (outcome.status !== 'stale') return
|
|
expect(outcome.reason).toMatch(/^missing /)
|
|
expect(fetchImpl).not.toHaveBeenCalled()
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('returns failed when both env and snapshot are missing', async () => {
|
|
const snapshotUrl = withSnapshotDir(null)
|
|
const outcome = await fetchRolesForBuild({
|
|
snapshotUrl,
|
|
fetchImpl: vi.fn() as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('failed')
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('does not retry on HTTP 401', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () => response({}, { status: 401 }))
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('stale')
|
|
if (outcome.status !== 'stale') return
|
|
expect(outcome.reason).toMatch(/^HTTP 401/)
|
|
expect(fetchImpl).toHaveBeenCalledTimes(1)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('retries 5xx up to the configured limit then falls back to snapshot', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () => response({}, { status: 503 }))
|
|
const sleep = vi.fn(async () => undefined)
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
retryDelaysMs: [1, 1, 1],
|
|
sleep,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('stale')
|
|
expect(fetchImpl).toHaveBeenCalledTimes(4)
|
|
expect(sleep).toHaveBeenCalledTimes(3)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('falls back to snapshot on envelope schema mismatch', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () => response({ apiVersion: '2', jobs: [] }))
|
|
const outcome = await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(outcome.status).toBe('stale')
|
|
if (outcome.status !== 'stale') return
|
|
expect(outcome.reason).toMatch(/^envelope schema/)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('memoizes within a single process', async () => {
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({ apiVersion: '1', jobs: [validJob()] })
|
|
)
|
|
const opts = {
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
}
|
|
const [a, b] = await Promise.all([
|
|
fetchRolesForBuild(opts),
|
|
fetchRolesForBuild(opts)
|
|
])
|
|
expect(a).toBe(b)
|
|
expect(fetchImpl).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('never writes to the snapshot file on success', async () => {
|
|
const snapshot = makeSnapshot()
|
|
const snapshotUrl = withSnapshotDir(snapshot)
|
|
const before = new URL(snapshotUrl.href)
|
|
const fs = await import('node:fs')
|
|
const initial = fs.readFileSync(before).toString()
|
|
const fetchImpl = vi.fn(async () =>
|
|
response({ apiVersion: '1', jobs: [validJob()] })
|
|
)
|
|
await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
const after = fs.readFileSync(before).toString()
|
|
expect(after).toBe(initial)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
|
|
it('does not retry on 4xx auth failures for 403', async () => {
|
|
const snapshotUrl = withSnapshotDir(makeSnapshot())
|
|
const fetchImpl = vi.fn(async () => response({}, { status: 403 }))
|
|
await fetchRolesForBuild({
|
|
apiKey: KEY,
|
|
boardName: BOARD,
|
|
baseUrl: BASE_URL,
|
|
snapshotUrl,
|
|
fetchImpl: fetchImpl as unknown as typeof fetch
|
|
})
|
|
expect(fetchImpl).toHaveBeenCalledTimes(1)
|
|
rmSync(new URL('.', snapshotUrl), { recursive: true, force: true })
|
|
})
|
|
})
|