mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-07 02:07:47 +08:00
- Bedrock: a user's AWS keys no longer go to an endpoint the server sets in AWS_ENDPOINT_URL_BEDROCK_RUNTIME / AWS_ENDPOINT_URL (read by the upgraded SDK), and admin panel keys win over AWS_BEARER_TOKEN_BEDROCK, as the Test button checks them. Checked with Bedrock. - Ollama: a server key without a base URL (admin panel, OLLAMA_API_KEY) goes to Ollama Cloud, as env.example says, instead of 127.0.0.1. - Quota: EdgeOne counts whatever key header comes along, keyless Ollama at a private address counts, and their provider texts stay in the log. - An EdgeOne server model (admin panel, ai-models.json) works: the route checked the raw provider header, which holds the name's slug. - parse-url ends downloads it does not read (too large, PDF, errors). - Desktop app: the port follows where the chats are (IndexedDB per origin) instead of a remembered port, which could hide them for good; a same-port restart tells the page to refetch the server models; a failed preset switch no longer undoes a newer choice; a presets file removed after a failed read can be saved again; .env values quoted from start to end keep their inner quotes, as dotenv reads them.
84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
// @vitest-environment node
|
|
import {
|
|
existsSync,
|
|
mkdtempSync,
|
|
readdirSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import { join } from "node:path"
|
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
|
|
|
const userData = vi.hoisted(() => ({ dir: "" }))
|
|
vi.mock("electron", () => ({
|
|
app: { getPath: () => userData.dir },
|
|
safeStorage: { isEncryptionAvailable: () => false },
|
|
}))
|
|
|
|
// Make the next read of the presets file fail, like a file an antivirus
|
|
// scanner holds on Windows
|
|
const readFails = vi.hoisted(() => ({ next: false }))
|
|
vi.mock("node:fs", async (importOriginal) => {
|
|
const fs = await importOriginal<typeof import("node:fs")>()
|
|
return {
|
|
...fs,
|
|
readFileSync: ((...args: Parameters<typeof fs.readFileSync>) => {
|
|
if (readFails.next && String(args[0]).endsWith(".json")) {
|
|
readFails.next = false
|
|
throw Object.assign(new Error("EBUSY: resource busy"), {
|
|
code: "EBUSY",
|
|
})
|
|
}
|
|
return fs.readFileSync(...args)
|
|
}) as typeof fs.readFileSync,
|
|
}
|
|
})
|
|
|
|
import { createPreset, loadPresets } from "@/electron/main/config-manager"
|
|
|
|
const presetsFile = () => join(userData.dir, "config-presets.json")
|
|
|
|
beforeEach(() => {
|
|
userData.dir = mkdtempSync(join(tmpdir(), "config-manager-"))
|
|
readFails.next = false
|
|
})
|
|
|
|
describe("config presets file", () => {
|
|
it("keeps a file it could not read for now", () => {
|
|
createPreset({ name: "Mine", config: { AI_PROVIDER: "openai" } })
|
|
|
|
readFails.next = true
|
|
expect(loadPresets().presets).toEqual([])
|
|
expect(existsSync(presetsFile())).toBe(true)
|
|
|
|
// A save based on that empty read must not replace the presets
|
|
readFails.next = true
|
|
expect(() =>
|
|
createPreset({ name: "New", config: { AI_PROVIDER: "openai" } }),
|
|
).toThrow()
|
|
expect(loadPresets().presets.map((p) => p.name)).toEqual(["Mine"])
|
|
})
|
|
|
|
it("saves again once a file it could not read is gone", () => {
|
|
createPreset({ name: "Mine", config: { AI_PROVIDER: "openai" } })
|
|
readFails.next = true
|
|
loadPresets()
|
|
// The user removes the file to start over
|
|
rmSync(presetsFile())
|
|
createPreset({ name: "New", config: { AI_PROVIDER: "openai" } })
|
|
expect(loadPresets().presets.map((p) => p.name)).toEqual(["New"])
|
|
})
|
|
|
|
it("moves a file that is not JSON aside", () => {
|
|
writeFileSync(presetsFile(), "{not json")
|
|
expect(loadPresets().presets).toEqual([])
|
|
expect(existsSync(presetsFile())).toBe(false)
|
|
expect(
|
|
readdirSync(userData.dir).some((f) =>
|
|
f.startsWith("config-presets.json.corrupt-"),
|
|
),
|
|
).toBe(true)
|
|
})
|
|
})
|