Files
supabase/apps/studio/tests/pages/integrations/vercel/install.utils.test.ts
Danny White 69570a357d fix(studio): route vercel deploy-button params to create despite marketplace source (#48258)
## What kind of change does this PR introduce?

Bug fix for the Vercel Deploy Button → Studio handoff.

## What is the current behavior?

Vercel sometimes opens our install popup with `source=marketplace` while
still sending Deploy Button params (`currentProjectId`, `external-id`).
We trust `source` alone, so users are routed to choose-project (connect)
instead of create — which is why create never gets reached in the Deploy
Button flow.

## What is the new behavior?

- When both Deploy Button signals (`currentProjectId` + `externalId`)
are present, route to create even if Vercel sent `source=marketplace` /
`external`
- Hide Skip (and related empty-state copy) on choose-project when those
signals are present, so Deploy Button users can't continue without
linking

## Additional context

Stacked on #48230.

Test plan:
- [ ] Unit tests for `resolveVercelInstallSource` /
`hasVercelDeployButtonSignals` pass
- [ ] Deploy Button flow with mislabeled `source=marketplace` + both
params → lands on create after org install/continue
- [ ] Genuine marketplace install (no `currentProjectId`/`external-id`)
→ still lands on choose-project with Skip available
- [ ] If choose-project is opened with both Deploy Button params, Skip
is hidden

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Improved Vercel installation handling for Deploy Button workflows,
ensuring the correct setup path is selected.
- Added clearer project-connection guidance when no projects are
available (including conditional skip copy).

- **Bug Fixes**
- Prevented Deploy Button installations from incorrectly offering a skip
option.
- Preserved the skip-and-connect-later guidance for other Vercel
installation flows.
- Improved recognition of Deploy Button installations even when the
reported Vercel source differs.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-07-25 00:00:37 +10:00

136 lines
4.2 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import {
buildVercelInstallRouteQuery,
getErrorMessage,
hasVercelDeployButtonSignals,
resolveVercelInstallSource,
} from '@/lib/integrations/vercel-install.utils'
describe('getErrorMessage', () => {
test('returns the message from an Error instance', () => {
expect(getErrorMessage(new Error('Something went wrong'))).toBe('Something went wrong')
})
test('returns the message from an error-like object', () => {
expect(getErrorMessage({ message: 'Request failed' })).toBe('Request failed')
})
test('returns undefined for non-string object messages', () => {
expect(getErrorMessage({ message: 404 })).toBeUndefined()
})
test('returns undefined for primitive and empty inputs', () => {
expect(getErrorMessage('Request failed')).toBeUndefined()
expect(getErrorMessage(null)).toBeUndefined()
expect(getErrorMessage(undefined)).toBeUndefined()
})
})
describe('buildVercelInstallRouteQuery', () => {
test('only keeps deploy-button destination params', () => {
expect(
buildVercelInstallRouteQuery({
source: 'deploy-button',
organizationSlug: 'acme',
currentProjectId: 'vercel-project',
externalId: 'github-repo',
next: 'https://vercel.com/callback',
configurationId: 'configuration-id',
})
).toStrictEqual({
organizationSlug: 'acme',
currentProjectId: 'vercel-project',
externalId: 'github-repo',
next: 'https://vercel.com/callback',
})
})
test('keeps marketplace params and passes through deploy-button ids when present', () => {
expect(
buildVercelInstallRouteQuery({
source: 'marketplace',
organizationSlug: 'acme',
configurationId: 'configuration-id',
currentProjectId: 'vercel-project',
externalId: 'github-repo',
next: 'https://vercel.com/callback',
})
).toStrictEqual({
organizationSlug: 'acme',
configurationId: 'configuration-id',
currentProjectId: 'vercel-project',
externalId: 'github-repo',
next: 'https://vercel.com/callback',
})
})
test('removes undefined params', () => {
expect(
buildVercelInstallRouteQuery({
source: 'external',
organizationSlug: 'acme',
configurationId: undefined,
next: undefined,
})
).toStrictEqual({ organizationSlug: 'acme' })
})
test('only keeps organizationSlug when source is undefined', () => {
expect(
buildVercelInstallRouteQuery({
source: undefined,
organizationSlug: 'acme',
configurationId: 'configuration-id',
currentProjectId: 'vercel-project',
externalId: 'github-repo',
next: 'https://vercel.com/callback',
})
).toStrictEqual({ organizationSlug: 'acme' })
})
})
describe('hasVercelDeployButtonSignals', () => {
test('requires both currentProjectId and externalId', () => {
expect(
hasVercelDeployButtonSignals({
currentProjectId: 'prj_123',
externalId: 'https://github.com/org/repo',
})
).toBe(true)
expect(hasVercelDeployButtonSignals({ currentProjectId: 'prj_123' })).toBe(false)
expect(hasVercelDeployButtonSignals({ externalId: 'https://github.com/org/repo' })).toBe(false)
expect(hasVercelDeployButtonSignals({})).toBe(false)
})
})
describe('resolveVercelInstallSource', () => {
test('overrides marketplace and external when deploy-button signals are present', () => {
expect(
resolveVercelInstallSource({
source: 'marketplace',
currentProjectId: 'prj_123',
externalId: 'https://github.com/org/repo',
})
).toBe('deploy-button')
expect(
resolveVercelInstallSource({
source: 'external',
currentProjectId: 'prj_123',
externalId: 'https://github.com/org/repo',
})
).toBe('deploy-button')
})
test('keeps the declared source when deploy-button signals are incomplete', () => {
expect(
resolveVercelInstallSource({
source: 'marketplace',
currentProjectId: 'prj_123',
})
).toBe('marketplace')
expect(resolveVercelInstallSource({ source: 'deploy-button' })).toBe('deploy-button')
expect(resolveVercelInstallSource({ source: undefined })).toBeUndefined()
})
})