Files
supabase/apps/studio/lib/api/generate-v4.test.ts
Matt Rossman fd5ef806d6 feat(studio): enable Assistant tracing for High Compliance projects (#50759)
Assistant chats from High Compliance projects now flow to Braintrust
like any other project. The constraint that required suppressing them no
longer applies, see AI-1241 for the details.

`isTracingAllowed` now takes only the project region to maintain EU
exclusion. Traces also carry an `isHighComplianceProject` metadata
field, so the project's status at the time of the trace is recorded
rather than looked up later against a setting customers can toggle.

To verify, see [this sample
trace](https://www.braintrust.dev/app/supabase.io/p/Assistant/logs?r=afabbdcc-aa89-446e-aa52-78aaa90d44a4&v=Production&s=afabbdcc-aa89-446e-aa52-78aaa90d44a4&tvt=trace)
from a High Compliance project on staging which indicates that tracing
is now enabled for these projects and that it carries metadata showing
the high compliance status.

| High Compliance project setting | `isHighComplianceProject` metadata |
|--------|--------|
| <img width="1554" height="454" alt="CleanShot 2026-09-22 at 5 14 58
PM@2x"
src="https://github.com/user-attachments/assets/23901c6e-0d79-44e8-a6dd-43cdedba1799"
/> | <img width="1674" height="990" alt="CleanShot 2026-09-22 at 5 17 40
PM@2x"
src="https://github.com/user-attachments/assets/fb2fba55-bcc1-4136-a432-b33a5c7f9ca2"
/> |

Closes AI-1241


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

## Summary by CodeRabbit

* **Changes**
* AI project compliance information is now represented by a unified
high-compliance project status.
* AI response tracing is now determined by project region: tracing
remains disabled for EU and unknown regions, while known non-EU regions
are eligible.
* AI feedback and SQL generation now use the updated compliance and
regional handling.
* **Tests**
* Updated coverage to reflect the revised compliance and tracing
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-23 09:39:01 -04:00

95 lines
2.4 KiB
TypeScript

import { safeSql } from '@supabase/pg-meta'
import { UIMessage } from 'ai'
import { expect, test, vi } from 'vitest'
import generateV4 from '../../pages/api/ai/sql/generate-v4'
import { getTools } from '@/lib/ai/tools'
import { sanitizeMessagePart } from '@/lib/ai/tools/tool-sanitizer'
vi.mock('@/lib/ai/tools/tool-sanitizer', () => ({
sanitizeMessagePart: vi.fn((part) => part),
}))
vi.mock('@/lib/ai/ai-details', () => ({
getAIDetails: vi.fn().mockResolvedValue({
aiOptInLevel: 'schema_and_log_and_data',
hasAccessToAdvanceModel: true,
region: 'us-east-1',
}),
}))
vi.mock('@/lib/ai/model', () => ({
getModel: vi.fn().mockResolvedValue({
modelParams: { model: {} },
systemProviderOptions: {},
}),
}))
vi.mock('@/data/sql/execute-sql-mutation', () => ({
executeSql: vi.fn().mockResolvedValue({ result: [] }),
}))
vi.mock('@/lib/ai/tools', () => ({
getTools: vi.fn().mockResolvedValue({}),
}))
vi.mock('ai', async () => {
const actual = await vi.importActual('ai')
return {
...actual,
streamText: vi.fn().mockReturnValue({
pipeUIMessageStreamToResponse: vi.fn(),
}),
}
})
test('generateV4 calls the tool sanitizer', async () => {
const mockReq = {
method: 'POST',
headers: {
authorization: 'Bearer test-token',
},
body: {
messages: [
{
id: 'test-msg-id',
role: 'assistant',
parts: [
{
type: 'tool-execute_sql',
state: 'output-available',
toolCallId: 'test-tool-call-id',
input: { sql: safeSql`SELECT * FROM users` },
output: [{ id: 1, name: 'test-output' }],
},
],
},
] satisfies UIMessage[],
projectRef: 'test-project',
connectionString: 'test-connection',
orgSlug: 'test-org',
supportMode: true,
},
on: vi.fn(),
}
const mockRes = {
status: vi.fn(() => mockRes),
json: vi.fn(() => mockRes),
setHeader: vi.fn(() => mockRes),
on: vi.fn(),
}
await generateV4(mockReq as any, mockRes as any)
expect(sanitizeMessagePart).toHaveBeenCalled()
expect(getTools).toHaveBeenCalledWith(
expect.objectContaining({
supportMode: true,
})
)
// The response 'close' event must be wired up so the remote MCP connection
// opened in getTools is torn down when the stream finishes or the client drops
expect(mockRes.on).toHaveBeenCalledWith('close', expect.any(Function))
})