Files
next-ai-draw-io/tests/unit/config-manager.test.ts
T
dayuan.jiang d5f31cb253 fix(server): use the keys the user sent, and more review fixes
Found by the second PR review:
- With AWS_BEARER_TOKEN_BEDROCK set on the server, a request with the
  user's AWS keys ran on the server's token: the Bedrock SDK prefers it.
  Checked with Bedrock: invalid user keys used to get an answer.
- An OpenAI key with the official URL filled in (the settings form does
  that) went to the Responses API. Back to main's rule: a configured base
  URL uses Chat Completions.
- A user's Ollama key went to the server's OLLAMA_BASE_URL, for chat and
  for the model list. Like every other provider, it goes to the user's
  base URL or Ollama Cloud.
- The server's keyless Ollama and EdgeOne were not counted in the quota.
- AI_MODEL models on the server's keys ran on any provider with a server
  key, not only on AI_PROVIDER.
- A user's Azure key without a base URL used the server's resource name.
- The admin panel's Test button failed whenever access codes were set.
- DeepSeek's errors in the stream (plain text) were shown as they were,
  without a hint and also on the server's keys. Bedrock's throttling in
  the stream was not recognised as a rate limit.
- The EdgeOne function accepted text/plain; x=application/json, which
  other sites can send without a CORS preflight.
- Desktop app: a launch that found the old port taken for a moment (the
  previous version still quitting after an update) remembered the new
  port for good. The new port is kept only when Windows reserves the old
  one. A failed read of the presets file moved it aside as corrupt, and a
  save could then replace the presets. Switching presets on the same port
  now reloads the page. The dev launcher no longer misses a preset change
  made before or during a restart.
2026-10-05 10:52:37 +09:00

68 lines
2.3 KiB
TypeScript

// @vitest-environment node
import { existsSync, mkdtempSync, readdirSync, 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("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)
})
})