Skip to content

Commit 43041a4

Browse files
anandgupta42claude
andcommitted
test: add Cortex E2E tests and sanitize hardcoded credentials
- Add `cortex-snowflake-e2e.test.ts` with 16 E2E tests for the Snowflake Cortex AI provider: PAT auth, streaming/non-streaming completions, model availability, request transforms, assistant-last rejection, PAT parsing - Tests skip via `describe.skipIf` when `SNOWFLAKE_CORTEX_PAT` is not set - Remove hardcoded credentials from `drivers-snowflake-e2e.test.ts` docstring — replaced with placeholder values Run with: export SNOWFLAKE_CORTEX_ACCOUNT="<account>" export SNOWFLAKE_CORTEX_PAT="<pat>" bun test test/altimate/cortex-snowflake-e2e.test.ts --timeout 120000 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bba5356 commit 43041a4

2 files changed

Lines changed: 314 additions & 1 deletion

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/**
2+
* Snowflake Cortex AI Provider E2E Tests
3+
*
4+
* Tests the Snowflake Cortex LLM inference endpoint via the provider's
5+
* auth plugin and request transforms. Requires a Snowflake PAT (Programmatic
6+
* Access Token) — password auth is NOT sufficient for the Cortex REST API.
7+
*
8+
* Env vars:
9+
*
10+
* # Required — PAT auth for Cortex API:
11+
* export SNOWFLAKE_CORTEX_ACCOUNT="ejjkbko-fub20041"
12+
* export SNOWFLAKE_CORTEX_PAT="<your-programmatic-access-token>"
13+
*
14+
* Create a PAT in Snowsight: Admin → Security → Programmatic Access Tokens
15+
*
16+
* Skips all tests if SNOWFLAKE_CORTEX_PAT is not set.
17+
*
18+
* Tests cover: PAT authentication, Cortex chat completions (streaming & non-streaming),
19+
* model availability, request transforms (max_tokens, tool stripping), error handling,
20+
* and the synthetic stop mechanism for assistant-last-message rejection.
21+
*
22+
* Run:
23+
* export SNOWFLAKE_CORTEX_ACCOUNT="ejjkbko-fub20041"
24+
* export SNOWFLAKE_CORTEX_PAT="<pat>"
25+
* bun test test/altimate/cortex-snowflake-e2e.test.ts --timeout 120000
26+
*/
27+
28+
import { describe, expect, test } from "bun:test"
29+
import {
30+
parseSnowflakePAT,
31+
transformSnowflakeBody,
32+
VALID_ACCOUNT_RE,
33+
} from "../../src/altimate/plugin/snowflake"
34+
35+
const CORTEX_ACCOUNT = process.env.SNOWFLAKE_CORTEX_ACCOUNT
36+
const CORTEX_PAT = process.env.SNOWFLAKE_CORTEX_PAT
37+
const HAS_CORTEX = !!(CORTEX_ACCOUNT && CORTEX_PAT)
38+
39+
function cortexBaseURL(account: string): string {
40+
return `https://${account}.snowflakecomputing.com/api/v2/cortex/v1`
41+
}
42+
43+
/** Make a raw Cortex chat completion request. */
44+
async function cortexChat(opts: {
45+
account: string
46+
pat: string
47+
model: string
48+
messages: Array<{ role: string; content: string }>
49+
stream?: boolean
50+
max_tokens?: number
51+
tools?: unknown[]
52+
}): Promise<Response> {
53+
const body: Record<string, unknown> = {
54+
model: opts.model,
55+
messages: opts.messages,
56+
max_completion_tokens: opts.max_tokens ?? 256,
57+
}
58+
if (opts.stream !== undefined) body.stream = opts.stream
59+
if (opts.tools) {
60+
body.tools = opts.tools
61+
body.tool_choice = "auto"
62+
}
63+
64+
return fetch(`${cortexBaseURL(opts.account)}/chat/completions`, {
65+
method: "POST",
66+
headers: {
67+
"Content-Type": "application/json",
68+
Authorization: `Bearer ${opts.pat}`,
69+
"X-Snowflake-Authorization-Token-Type": "PROGRAMMATIC_ACCESS_TOKEN",
70+
},
71+
body: JSON.stringify(body),
72+
})
73+
}
74+
75+
// ---------------------------------------------------------------------------
76+
// Cortex API E2E (requires PAT)
77+
// ---------------------------------------------------------------------------
78+
79+
describe.skipIf(!HAS_CORTEX)("Snowflake Cortex E2E", () => {
80+
// -------------------------------------------------------------------------
81+
// Authentication
82+
// -------------------------------------------------------------------------
83+
describe("Authentication", () => {
84+
test("PAT auth succeeds with valid credentials", async () => {
85+
const resp = await cortexChat({
86+
account: CORTEX_ACCOUNT!,
87+
pat: CORTEX_PAT!,
88+
model: "claude-3-5-sonnet",
89+
messages: [{ role: "user", content: "Reply with exactly: hello" }],
90+
stream: false,
91+
max_tokens: 32,
92+
})
93+
expect(resp.status).toBe(200)
94+
const json = await resp.json()
95+
expect(json.choices).toBeDefined()
96+
expect(json.choices.length).toBeGreaterThan(0)
97+
expect(json.choices[0].message.content).toBeTruthy()
98+
}, 30000)
99+
100+
test("rejects invalid PAT", async () => {
101+
const resp = await cortexChat({
102+
account: CORTEX_ACCOUNT!,
103+
pat: "invalid-pat-token",
104+
model: "claude-3-5-sonnet",
105+
messages: [{ role: "user", content: "test" }],
106+
stream: false,
107+
max_tokens: 16,
108+
})
109+
expect(resp.status).toBeGreaterThanOrEqual(400)
110+
}, 15000)
111+
112+
test("rejects invalid account identifier", async () => {
113+
const resp = await cortexChat({
114+
account: "nonexistent-account-xyz123",
115+
pat: CORTEX_PAT!,
116+
model: "claude-3-5-sonnet",
117+
messages: [{ role: "user", content: "test" }],
118+
stream: false,
119+
max_tokens: 16,
120+
})
121+
// DNS resolution or connection error — fetch throws or returns error
122+
expect(resp.ok).toBe(false)
123+
}, 15000)
124+
})
125+
126+
// -------------------------------------------------------------------------
127+
// Non-streaming completions
128+
// -------------------------------------------------------------------------
129+
describe("Non-Streaming Completions", () => {
130+
test("returns valid JSON completion", async () => {
131+
const resp = await cortexChat({
132+
account: CORTEX_ACCOUNT!,
133+
pat: CORTEX_PAT!,
134+
model: "claude-3-5-sonnet",
135+
messages: [{ role: "user", content: "What is 2+2? Reply with just the number." }],
136+
stream: false,
137+
max_tokens: 16,
138+
})
139+
expect(resp.status).toBe(200)
140+
const json = await resp.json()
141+
expect(json.choices[0].message.role).toBe("assistant")
142+
expect(json.choices[0].message.content).toContain("4")
143+
expect(json.choices[0].finish_reason).toBe("stop")
144+
}, 30000)
145+
146+
test("uses max_completion_tokens (not max_tokens)", async () => {
147+
// Cortex uses max_completion_tokens — verify it works
148+
const resp = await fetch(`${cortexBaseURL(CORTEX_ACCOUNT!)}/chat/completions`, {
149+
method: "POST",
150+
headers: {
151+
"Content-Type": "application/json",
152+
Authorization: `Bearer ${CORTEX_PAT}`,
153+
"X-Snowflake-Authorization-Token-Type": "PROGRAMMATIC_ACCESS_TOKEN",
154+
},
155+
body: JSON.stringify({
156+
model: "claude-3-5-sonnet",
157+
messages: [{ role: "user", content: "Say hello" }],
158+
max_completion_tokens: 16,
159+
stream: false,
160+
}),
161+
})
162+
expect(resp.status).toBe(200)
163+
}, 30000)
164+
})
165+
166+
// -------------------------------------------------------------------------
167+
// Streaming completions
168+
// -------------------------------------------------------------------------
169+
describe("Streaming Completions", () => {
170+
test("returns SSE stream with chunked deltas", async () => {
171+
const resp = await cortexChat({
172+
account: CORTEX_ACCOUNT!,
173+
pat: CORTEX_PAT!,
174+
model: "claude-3-5-sonnet",
175+
messages: [{ role: "user", content: "Count from 1 to 5." }],
176+
stream: true,
177+
max_tokens: 64,
178+
})
179+
expect(resp.status).toBe(200)
180+
expect(resp.headers.get("content-type")).toContain("text/event-stream")
181+
182+
const text = await resp.text()
183+
expect(text).toContain("data: ")
184+
expect(text).toContain('"finish_reason":"stop"')
185+
expect(text).toContain("data: [DONE]")
186+
}, 30000)
187+
})
188+
189+
// -------------------------------------------------------------------------
190+
// Model availability
191+
// -------------------------------------------------------------------------
192+
describe("Model Availability", () => {
193+
const models = ["claude-3-5-sonnet", "llama3.3-70b", "mistral-large2"]
194+
195+
for (const model of models) {
196+
test(`model ${model} responds`, async () => {
197+
const resp = await cortexChat({
198+
account: CORTEX_ACCOUNT!,
199+
pat: CORTEX_PAT!,
200+
model,
201+
messages: [{ role: "user", content: "Reply with: ok" }],
202+
stream: false,
203+
max_tokens: 8,
204+
})
205+
// Accept 200 (model available) or 400 (model not enabled for this account)
206+
expect([200, 400]).toContain(resp.status)
207+
}, 30000)
208+
}
209+
})
210+
211+
// -------------------------------------------------------------------------
212+
// Request transforms (via transformSnowflakeBody)
213+
// -------------------------------------------------------------------------
214+
describe("Request Transform Integration", () => {
215+
test("max_tokens renamed to max_completion_tokens in transform", () => {
216+
const input = JSON.stringify({
217+
model: "claude-3-5-sonnet",
218+
messages: [{ role: "user", content: "test" }],
219+
max_tokens: 100,
220+
stream: true,
221+
})
222+
const { body } = transformSnowflakeBody(input)
223+
const parsed = JSON.parse(body)
224+
expect(parsed.max_completion_tokens).toBe(100)
225+
expect(parsed.max_tokens).toBeUndefined()
226+
})
227+
228+
test("tools stripped for llama model in transform", () => {
229+
const input = JSON.stringify({
230+
model: "llama3.3-70b",
231+
messages: [{ role: "user", content: "test" }],
232+
tools: [{ type: "function", function: { name: "read_file" } }],
233+
tool_choice: "auto",
234+
})
235+
const { body } = transformSnowflakeBody(input)
236+
const parsed = JSON.parse(body)
237+
expect(parsed.tools).toBeUndefined()
238+
expect(parsed.tool_choice).toBeUndefined()
239+
})
240+
241+
test("synthetic stop skipped for non-streaming requests", () => {
242+
const input = JSON.stringify({
243+
model: "claude-3-5-sonnet",
244+
stream: false,
245+
messages: [
246+
{ role: "user", content: "test" },
247+
{ role: "assistant", content: "response" },
248+
],
249+
})
250+
const { syntheticStop } = transformSnowflakeBody(input)
251+
expect(syntheticStop).toBeUndefined()
252+
})
253+
254+
test("synthetic stop triggered for streaming requests with trailing assistant", () => {
255+
const input = JSON.stringify({
256+
model: "claude-3-5-sonnet",
257+
stream: true,
258+
messages: [
259+
{ role: "user", content: "test" },
260+
{ role: "assistant", content: "response" },
261+
],
262+
})
263+
const { syntheticStop } = transformSnowflakeBody(input)
264+
expect(syntheticStop).toBeDefined()
265+
expect(syntheticStop!.status).toBe(200)
266+
})
267+
})
268+
269+
// -------------------------------------------------------------------------
270+
// Cortex rejects assistant-last messages
271+
// -------------------------------------------------------------------------
272+
describe("Assistant-Last Message Rejection", () => {
273+
test("Cortex rejects request with trailing assistant message", async () => {
274+
const resp = await fetch(`${cortexBaseURL(CORTEX_ACCOUNT!)}/chat/completions`, {
275+
method: "POST",
276+
headers: {
277+
"Content-Type": "application/json",
278+
Authorization: `Bearer ${CORTEX_PAT}`,
279+
"X-Snowflake-Authorization-Token-Type": "PROGRAMMATIC_ACCESS_TOKEN",
280+
},
281+
body: JSON.stringify({
282+
model: "claude-3-5-sonnet",
283+
messages: [
284+
{ role: "user", content: "hello" },
285+
{ role: "assistant", content: "I'm here" },
286+
],
287+
max_completion_tokens: 16,
288+
stream: false,
289+
}),
290+
})
291+
// Cortex should reject this — the synthetic stop mechanism exists to prevent this
292+
expect(resp.status).toBeGreaterThanOrEqual(400)
293+
}, 15000)
294+
})
295+
296+
// -------------------------------------------------------------------------
297+
// PAT parsing validation
298+
// -------------------------------------------------------------------------
299+
describe("PAT Parsing with Real Credentials", () => {
300+
test("parses real account::pat format", () => {
301+
const account = CORTEX_ACCOUNT!
302+
const pat = CORTEX_PAT!
303+
const result = parseSnowflakePAT(`${account}::${pat}`)
304+
expect(result).not.toBeNull()
305+
expect(result!.account).toBe(account)
306+
expect(result!.token).toBe(pat)
307+
})
308+
309+
test("account passes validation regex", () => {
310+
expect(VALID_ACCOUNT_RE.test(CORTEX_ACCOUNT!)).toBe(true)
311+
})
312+
})
313+
})

packages/opencode/test/altimate/drivers-snowflake-e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Requires env vars (set one or more):
55
*
66
* # Password auth (primary):
7-
* export ALTIMATE_CODE_CONN_SNOWFLAKE_TEST='{"type":"snowflake","account":"ejjkbko-fub20041","user":"juleszobi","password":"Ejungle9!","warehouse":"COMPUTE_WH","database":"TENANT_INFORMATICA_MIGRATION","schema":"public","role":"ACCOUNTADMIN"}'
7+
* export ALTIMATE_CODE_CONN_SNOWFLAKE_TEST='{"type":"snowflake","account":"<account>","user":"<user>","password":"<password>","warehouse":"<warehouse>","database":"<database>","schema":"public","role":"ACCOUNTADMIN"}'
88
*
99
* # Key-pair auth (optional — requires RSA key setup in Snowflake):
1010
* export SNOWFLAKE_TEST_KEY_PATH="/path/to/rsa_key.p8"

0 commit comments

Comments
 (0)