logancyang_obsidian-copilot/__mocks__/obsidian.js
Zero Liu 01a01e2951
fix(brevilabs): rewrite uploads via requestUrl multipart (W8/9) (#2399)
* fix(brevilabs): rewrite uploads via requestUrl multipart (W8/9)

Ninth of nine source workspaces splitting #2397. Replaces native
fetch(FormData) calls in brevilabsClient with Obsidian's requestUrl
plus a manually-constructed multipart body. This is required because
Obsidian's CORS-bypass path can't handle native FormData streams.

- src/LLMProviders/brevilabsClient.ts: buildMultipartFromFormData
  helper builds a Uint8Array body with explicit boundary; uploads now
  flow through requestUrl with a Content-Type header that includes
  the boundary string
- __mocks__/obsidian.js: requestUrl jest mock + __setRequestUrlImpl
  helper so tests can swap in per-case responses

Manual verification required: image upload, PDF upload, audio
transcription, error path. Plus license needed to exercise real
uploads.

W0 already merged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(network): dedupe brevilabs response parsing + bound model-fetch timeout

- Extract parseBrevilabsResponse helper so makeRequest and
  makeFormDataRequest share one parse/error path.
- Wrap fetchModelsForProvider's safeFetch in a 10s Promise.race
  (safeFetch ignores AbortSignal, so a dead base URL could hang
  the model-import dialog forever).
- Preserve real @/utils exports in selfHostServices test mock via
  jest.requireActual.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(lint): fix typescript-eslint violations in brevilabs + tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 11:42:25 -07:00

124 lines
4 KiB
JavaScript

// __mocks__/obsidian.js
import { parse as parseYamlString } from "yaml";
// Per-test overrides set via the exported `__setRequestUrlImpl` helper.
// Default: empty success response. Tests that exercise network paths should
// install their own implementation.
let requestUrlImpl = jest.fn().mockResolvedValue({
status: 200,
text: "",
json: undefined,
arrayBuffer: new ArrayBuffer(0),
headers: {},
});
module.exports = {
// Reason: normalizePath is used by projectPaths.ts; identity function is sufficient for tests
normalizePath: jest.fn().mockImplementation((p) => p),
moment: jest.requireActual("moment"),
requestUrl: (...args) => requestUrlImpl(...args),
__setRequestUrlImpl: (impl) => {
requestUrlImpl = impl;
},
Vault: jest.fn().mockImplementation(() => {
return {
getMarkdownFiles: jest.fn().mockImplementation(() => {
// Return an array of mock markdown file objects
return [
{ path: "test/test2/note1.md" },
{ path: "test/note2.md" },
{ path: "test2/note3.md" },
{ path: "note4.md" },
];
}),
cachedRead: jest.fn().mockImplementation((file) => {
// Simulate reading file contents. You can adjust the content as needed for your tests.
const fileContents = {
"test/test2/note1.md": "---\ntags: [Tag1, tag2]\n---\nContent of note1",
"test/note2.md": "---\ntags: [tag2, tag3]\n---\nContent of note2",
"test2/note3.md": "something else ---\ntags: [false_tag]\n---\nContent of note3",
"note4.md": "---\ntags: [tag1, Tag4]\n---\nContent of note4",
};
return Promise.resolve(fileContents[file.path]);
}),
};
}),
Platform: {
isDesktop: true,
},
parseYaml: jest.fn().mockImplementation((content) => {
return parseYamlString(content);
}),
Modal: class Modal {
constructor() {
this.open = jest.fn();
this.close = jest.fn();
this.onOpen = jest.fn();
this.onClose = jest.fn();
}
},
App: jest.fn().mockImplementation(() => ({
workspace: {
getActiveFile: jest.fn(),
},
vault: {
read: jest.fn(),
},
})),
ItemView: jest.fn().mockImplementation(function () {
this.containerEl = window.document.createElement("div");
this.onOpen = jest.fn();
this.onClose = jest.fn();
this.getDisplayText = jest.fn().mockReturnValue("Mock View");
this.getViewType = jest.fn().mockReturnValue("mock-view");
this.getIcon = jest.fn().mockReturnValue("document");
}),
Notice: jest.fn().mockImplementation(function (message) {
this.message = message;
this.noticeEl = window.document.createElement("div");
this.hide = jest.fn();
}),
TFile: jest.fn().mockImplementation(function (path) {
this.path = path;
this.name = path.split("/").pop();
this.basename = this.name.replace(/\.[^/.]+$/, "");
this.extension = path.split(".").pop();
}),
TFolder: jest.fn().mockImplementation(function (path) {
this.path = path || "";
this.name = this.path.split("/").pop() || "";
}),
WorkspaceLeaf: jest.fn().mockImplementation(function () {
this.view = null;
this.setViewState = jest.fn();
this.detach = jest.fn();
this.getViewState = jest.fn().mockReturnValue({});
}),
};
// Mock the global app object
window.app = {
vault: {
getAbstractFileByPath: jest.fn().mockReturnValue({
name: "test-file.md",
path: "test-file.md",
}),
read: jest.fn().mockResolvedValue("test content"),
modify: jest.fn().mockResolvedValue(undefined),
getMarkdownFiles: jest.fn().mockReturnValue([]),
getAllLoadedFiles: jest.fn().mockReturnValue([]),
},
workspace: {
getActiveFile: jest.fn().mockReturnValue(null),
getLeaf: jest.fn().mockReturnValue({
openFile: jest.fn().mockResolvedValue(undefined),
}),
},
metadataCache: {
getFirstLinkpathDest: jest.fn().mockReturnValue(null),
getFileCache: jest.fn().mockReturnValue(null),
},
fileManager: {
trashFile: jest.fn().mockResolvedValue(undefined),
},
};