Skip to content
Open
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 change: 1 addition & 0 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ CRON_SECRET=your_cron_secret # Use `openssl rand -hex 32` to generate. Authentic
# FORKING_ENABLED= # Workspace forks
# CREDENTIAL_GROUPS= # Enterprise managed OAuth collections
# TABLE_ROW_TTL= # Table TTL columns and expired-row cleanup
# TABLE_REFERENCE_COLUMNS= # Table Reference columns
# KNOWLEDGE_MEMBER_ACCESS= # Per-member knowledge connectors and hybrid-by-default retrieval
# ORGANIZATIONS_ENABLED= / NEXT_PUBLIC_ORGANIZATIONS_ENABLED= # Organizations only

Expand Down
52 changes: 51 additions & 1 deletion apps/sim/app/api/table/[tableId]/columns/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ vi.mock('@/lib/table/wire', () => ({
vi.mock('@/app/api/table/utils', () => ({
accessError: () => new Response('denied', { status: 403 }),
checkAccess: mockCheckAccess,
orchestrationErrorResponse: (error: unknown) =>
error instanceof OrchestrationError
? NextResponse.json(
{ error: error.message },
{ status: statusForOrchestrationError(error.code) }
)
: null,
orchestrationOutcomeErrorResponse: (
outcome: { error?: string; errorCode?: OrchestrationErrorCode },
fallback: string
Expand All @@ -73,7 +80,7 @@ import {
type OrchestrationErrorCode,
statusForOrchestrationError,
} from '@/lib/core/orchestration/types'
import { PATCH } from '@/app/api/table/[tableId]/columns/route'
import { PATCH, POST } from '@/app/api/table/[tableId]/columns/route'

const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111'

Expand All @@ -88,6 +95,49 @@ function patch(updates: Record<string, unknown>) {
)
}

function post(column: Record<string, unknown>) {
return POST(
new NextRequest('http://localhost/api/table/t1/columns', {
method: 'POST',
body: JSON.stringify({ workspaceId: WORKSPACE_ID, column }),
headers: { 'content-type': 'application/json' },
}),
{ params: Promise.resolve({ tableId: 't1' }) }
)
}

describe('POST /api/table/[tableId]/columns — Reference feature gate', () => {
beforeEach(() => {
vi.clearAllMocks()
hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
success: true,
userId: 'user-1',
authType: 'session',
})
mockCheckAccess.mockResolvedValue({
ok: true,
table: { workspaceId: WORKSPACE_ID, schema: { columns: [] } },
})
})

it('returns 403 when Reference columns are disabled', async () => {
mockAddTableColumn.mockRejectedValue(
new OrchestrationError('forbidden', 'Reference columns are not enabled for this deployment')
)

const response = await post({
name: 'Account',
type: 'reference',
referenceTableId: 'tbl_accounts',
})

expect(response.status).toBe(403)
expect(await response.json()).toEqual({
error: 'Reference columns are not enabled for this deployment',
})
})
})

describe('PATCH /api/table/[tableId]/columns — pre-flight guards', () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/app/api/table/[tableId]/columns/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { normalizeColumn } from '@/lib/table/wire'
import {
accessError,
checkAccess,
orchestrationErrorResponse,
orchestrationOutcomeErrorResponse,
rootErrorMessage,
tableLockErrorResponse,
Expand Down Expand Up @@ -69,6 +70,9 @@ export const POST = withRouteHandler(async (request: NextRequest, context: Colum
return validationErrorResponse(error, 'Invalid request data')
}

const classified = orchestrationErrorResponse(error)
if (classified) return classified

const msg = rootErrorMessage(error)
if (
msg.includes('already exists') ||
Expand Down
Loading
Loading