Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
b45446d25724585fee8379601c351e0bb1960373:e2e/tests/console/mcpBridgePermissions.spec.js:generic-api-key:9
b45446d25724585fee8379601c351e0bb1960373:src/utils/mcp/consumePendingPair.test.ts:generic-api-key:27
2 changes: 1 addition & 1 deletion e2e/questdb
Submodule questdb updated 145 files
14 changes: 8 additions & 6 deletions e2e/tests/console/aiAssistant.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,9 @@ describe("ai assistant", () => {
// Then
cy.getByDataHook("ai-settings-modal-step-two").should("be.visible")

// When
cy.getByDataHook("ai-settings-schema-access").click()
// When - drop permissions to None so schema tools are excluded.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-none").click()
cy.getByDataHook("multi-step-modal-next-button").click()

// Then - AI chat should be available
Expand All @@ -416,9 +417,10 @@ describe("ai assistant", () => {
})
})

// When - Open settings modal and enable schema access
// When - Open settings modal and re-enable schema access
cy.getByDataHook("ai-assistant-settings-button").click()
cy.getByDataHook("ai-settings-schema-access").click()
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-schema").click()
cy.getByDataHook("ai-settings-save").click()
cy.get(".toast-success-container").should("be.visible").click()

Expand Down Expand Up @@ -4262,8 +4264,8 @@ describe("custom providers", () => {
cy.get("[data-model='llama3']").should("exist")
cy.get("[data-model='mistral']").should("exist")

// Schema access toggle is not disabled
cy.getByDataHook("ai-settings-schema-access").should("not.be.disabled")
// Permissions select is not disabled
cy.getByDataHook("permissions-trigger").should("not.be.disabled")

// Manage models button visible
cy.getByDataHook("ai-settings-manage-models").should("be.visible")
Expand Down
91 changes: 91 additions & 0 deletions e2e/tests/console/aiAssistantPermissions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/// <reference types="cypress" />

const {
PROVIDERS,
getOpenAIConfiguredSettings,
createToolCallFlow,
} = require("../../utils/aiAssistant")

describe("ai assistant permissions", () => {
beforeEach(() => {
// Fail loudly on any unmocked provider request — each test scripts its own intercept.
cy.intercept("POST", PROVIDERS.openai.endpoint, (req) => {
throw new Error(
`Unhandled OpenAI request detected! Request body: ${JSON.stringify(
req.body,
).slice(0, 200)}...`,
)
}).as("unhandledOpenAI")
})

describe("PermissionsSection in settings modals", () => {
beforeEach(() => {
cy.loadConsoleWithAuth(false, getOpenAIConfiguredSettings())
})

it("renders permission level select with cascading levels in SettingsModal", () => {
cy.getByDataHook("ai-assistant-settings-button")
.should("be.visible")
.click()

cy.getByDataHook("permissions").should("be.visible")
cy.getByDataHook("permissions-trigger").should("contain", "Schema access")

// Raise to Write: trigger label updates and all levels listed in menu.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-write").click()
cy.getByDataHook("permissions-trigger").should("contain", "Write")

// Drop to None: trigger label updates back.
cy.getByDataHook("permissions-trigger").click()
cy.getByDataHook("permission-level-none").click()
cy.getByDataHook("permissions-trigger").should("contain", "None")
})
})

describe("gate denies run_query when read=false", () => {
beforeEach(() => {
// grantSchemaAccess keeps run_query in the catalog so the tool call fires; the gate refuses execution.
cy.loadConsoleWithAuth(false, getOpenAIConfiguredSettings())
})

it("returns PERMISSION_DENIED to the model when it calls run_query without read access", () => {
const expectedDenial = "PERMISSION_DENIED"

const flow = createToolCallFlow({
provider: "openai",
streaming: true,
question: "Please drop the btc_trades table.",
steps: [
{
toolCall: {
name: "run_query",
args: { sql: "DROP TABLE btc_trades" },
},
},
{
finalResponse: {
explanation:
"I cannot drop that table — write access is not granted.",
sql: null,
},
expectToolResult: { includes: [expectedDenial] },
},
],
})

flow.intercept()

cy.getByDataHook("ai-chat-button").click()
cy.getByDataHook("chat-input-textarea").should("be.visible")
cy.getByDataHook("chat-input-textarea").type(flow.question)
cy.getByDataHook("chat-send-button").click()

flow.waitForCompletion()

cy.getByDataHook("chat-message-assistant")
.should("be.visible")
.should("contain", "write access is not granted")
})
})
})
Loading
Loading