mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-10 11:39:50 +08:00
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
// @vitest-environment node
|
|||
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||
|
|
import { POST as chat } from "@/app/api/chat/route"
|
||
|
|
import { POST as parseUrl } from "@/app/api/parse-url/route"
|
||
|
|
import { POST as providerModels } from "@/app/api/provider-models/route"
|
||
|
|
import { POST as validateDiagram } from "@/app/api/validate-diagram/route"
|
||
|
|
import { POST as validateModel } from "@/app/api/validate-model/route"
|
||
|
|
|
||
|
|
// The routes that run models or fetch URLs, which a page on another site
|
||
|
|
// could otherwise make the user's own server do
|
||
|
|
const ROUTES = {
|
||
|
|
chat,
|
||
|
|
"parse-url": parseUrl,
|
||
|
|
"provider-models": providerModels,
|
||
|
|
"validate-diagram": validateDiagram,
|
||
|
|
"validate-model": validateModel,
|
||
|
|
}
|
||
|
|
|
||
|
|
const body = JSON.stringify({
|
||
|
|
messages: [
|
||
|
|
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
||
|
|
],
|
||
|
|
url: "https://example.com",
|
||
|
|
provider: "openai",
|
||
|
|
apiKey: "k",
|
||
|
|
modelId: "gpt-5.5",
|
||
|
|
imageData: "data:image/png;base64,AAAA",
|
||
|
|
})
|
||
|
|
|
||
|
|
const saved = process.env.NEXT_AI_DRAWIO_DESKTOP
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.stubGlobal(
|
||
|
|
"fetch",
|
||
|
|
vi.fn(async () => {
|
||
|
|
throw new Error("no network in tests")
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
if (saved === undefined) delete process.env.NEXT_AI_DRAWIO_DESKTOP
|
||
|
|
else process.env.NEXT_AI_DRAWIO_DESKTOP = saved
|
||
|
|
vi.unstubAllGlobals()
|
||
|
|
})
|
||
|
|
|
||
|
|
describe("requests another website could send", () => {
|
||
|
|
for (const [name, post] of Object.entries(ROUTES)) {
|
||
|
|
it(`${name}: refuses a text body, which needs no CORS preflight`, async () => {
|
||
|
|
// fetch(..., { mode: "no-cors", body: JSON.stringify(...) })
|
||
|
|
// from another site arrives as text/plain
|
||
|
|
const res = await post(
|
||
|
|
new Request(`http://127.0.0.1:61337/api/${name}`, {
|
||
|
|
method: "POST",
|
||
|
|
body,
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
expect(res.status).toBe(415)
|
||
|
|
expect(fetch).not.toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
|
||
|
|
it(`${name}: desktop app refuses a foreign Host (DNS rebinding)`, async () => {
|
||
|
|
process.env.NEXT_AI_DRAWIO_DESKTOP = "1"
|
||
|
|
const res = await post(
|
||
|
|
new Request(`http://127.0.0.1:61337/api/${name}`, {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
host: "rebind.attacker.example:61337",
|
||
|
|
},
|
||
|
|
body,
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
expect(res.status).toBe(403)
|
||
|
|
expect(fetch).not.toHaveBeenCalled()
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
it("lets the desktop window's own requests through", async () => {
|
||
|
|
process.env.NEXT_AI_DRAWIO_DESKTOP = "1"
|
||
|
|
const res = await validateModel(
|
||
|
|
new Request("http://127.0.0.1:61337/api/validate-model", {
|
||
|
|
method: "POST",
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json; charset=utf-8",
|
||
|
|
host: "127.0.0.1:61337",
|
||
|
|
},
|
||
|
|
body: JSON.stringify({ provider: "openai" }),
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
// Past the check: the route's own validation answers
|
||
|
|
expect(res.status).toBe(400)
|
||
|
|
expect(await res.text()).toMatch(/required/)
|
||
|
|
})
|
||
|
|
})
|