@lobehub/chat 1.136.13 → 1.137.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/.cursor/rules/add-setting-env.mdc +175 -0
  2. package/.cursor/rules/db-migrations.mdc +25 -0
  3. package/.env.example +7 -0
  4. package/CHANGELOG.md +50 -0
  5. package/Dockerfile +3 -2
  6. package/Dockerfile.database +15 -3
  7. package/Dockerfile.pglite +3 -2
  8. package/changelog/v1.json +18 -0
  9. package/docs/development/database-schema.dbml +1 -0
  10. package/docs/self-hosting/advanced/feature-flags.mdx +25 -15
  11. package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +25 -15
  12. package/docs/self-hosting/environment-variables/basic.mdx +12 -0
  13. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +12 -0
  14. package/locales/ar/setting.json +8 -0
  15. package/locales/bg-BG/setting.json +8 -0
  16. package/locales/de-DE/setting.json +8 -0
  17. package/locales/en-US/setting.json +8 -0
  18. package/locales/es-ES/setting.json +8 -0
  19. package/locales/fa-IR/setting.json +8 -0
  20. package/locales/fr-FR/setting.json +8 -0
  21. package/locales/it-IT/setting.json +8 -0
  22. package/locales/ja-JP/setting.json +8 -0
  23. package/locales/ko-KR/setting.json +8 -0
  24. package/locales/nl-NL/setting.json +8 -0
  25. package/locales/pl-PL/setting.json +8 -0
  26. package/locales/pt-BR/setting.json +8 -0
  27. package/locales/ru-RU/setting.json +8 -0
  28. package/locales/tr-TR/setting.json +8 -0
  29. package/locales/vi-VN/setting.json +8 -0
  30. package/locales/zh-CN/setting.json +8 -0
  31. package/locales/zh-TW/setting.json +8 -0
  32. package/package.json +1 -1
  33. package/packages/agent-runtime/examples/tools-calling.ts +4 -3
  34. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +559 -29
  35. package/packages/agent-runtime/src/core/runtime.ts +171 -43
  36. package/packages/agent-runtime/src/types/instruction.ts +32 -6
  37. package/packages/agent-runtime/src/types/runtime.ts +2 -2
  38. package/packages/agent-runtime/src/types/state.ts +1 -8
  39. package/packages/agent-runtime/vitest.config.mts +14 -0
  40. package/packages/const/src/settings/image.ts +8 -0
  41. package/packages/const/src/settings/index.ts +3 -0
  42. package/packages/context-engine/src/__tests__/pipeline.test.ts +485 -0
  43. package/packages/context-engine/src/base/__tests__/BaseProcessor.test.ts +381 -0
  44. package/packages/context-engine/src/base/__tests__/BaseProvider.test.ts +392 -0
  45. package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +346 -0
  46. package/packages/context-engine/src/processors/__tests__/ToolCall.test.ts +552 -0
  47. package/packages/database/migrations/0038_add_image_user_settings.sql +1 -0
  48. package/packages/database/migrations/meta/0038_snapshot.json +7580 -0
  49. package/packages/database/migrations/meta/_journal.json +7 -0
  50. package/packages/database/src/core/migrations.json +6 -0
  51. package/packages/database/src/models/user.ts +3 -1
  52. package/packages/database/src/schemas/user.ts +1 -0
  53. package/packages/file-loaders/src/loaders/docx/index.test.ts +0 -1
  54. package/packages/file-loaders/src/loaders/excel/__snapshots__/index.test.ts.snap +30 -0
  55. package/packages/file-loaders/src/loaders/excel/index.test.ts +8 -0
  56. package/packages/file-loaders/src/loaders/pptx/index.test.ts +25 -0
  57. package/packages/file-loaders/src/utils/parser-utils.test.ts +155 -0
  58. package/packages/file-loaders/vitest.config.mts +8 -0
  59. package/packages/model-runtime/CLAUDE.md +5 -0
  60. package/packages/model-runtime/docs/test-coverage.md +706 -0
  61. package/packages/model-runtime/src/core/ModelRuntime.test.ts +231 -0
  62. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +1 -1
  63. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +799 -0
  64. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +188 -4
  65. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +41 -10
  66. package/packages/model-runtime/src/core/streams/openai/__snapshots__/responsesStream.test.ts.snap +439 -0
  67. package/packages/model-runtime/src/core/streams/openai/openai.test.ts +789 -0
  68. package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +551 -0
  69. package/packages/model-runtime/src/core/usageConverters/utils/computeChatCost.test.ts +230 -0
  70. package/packages/model-runtime/src/core/usageConverters/utils/computeImageCost.test.ts +334 -37
  71. package/packages/model-runtime/src/providerTestUtils.ts +148 -145
  72. package/packages/model-runtime/src/providers/ai302/index.test.ts +60 -0
  73. package/packages/model-runtime/src/providers/ai302/index.ts +9 -4
  74. package/packages/model-runtime/src/providers/ai360/index.test.ts +1213 -1
  75. package/packages/model-runtime/src/providers/ai360/index.ts +9 -4
  76. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +73 -0
  77. package/packages/model-runtime/src/providers/aihubmix/index.ts +6 -9
  78. package/packages/model-runtime/src/providers/akashchat/index.test.ts +433 -3
  79. package/packages/model-runtime/src/providers/akashchat/index.ts +12 -7
  80. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +183 -29
  81. package/packages/model-runtime/src/providers/anthropic/generateObject.ts +40 -24
  82. package/packages/model-runtime/src/providers/azureai/index.test.ts +102 -0
  83. package/packages/model-runtime/src/providers/baichuan/index.test.ts +416 -26
  84. package/packages/model-runtime/src/providers/baichuan/index.ts +23 -20
  85. package/packages/model-runtime/src/providers/bedrock/index.test.ts +420 -2
  86. package/packages/model-runtime/src/providers/cerebras/index.test.ts +465 -0
  87. package/packages/model-runtime/src/providers/cerebras/index.ts +8 -3
  88. package/packages/model-runtime/src/providers/cohere/index.test.ts +1074 -1
  89. package/packages/model-runtime/src/providers/cohere/index.ts +8 -3
  90. package/packages/model-runtime/src/providers/cometapi/index.test.ts +439 -3
  91. package/packages/model-runtime/src/providers/cometapi/index.ts +8 -3
  92. package/packages/model-runtime/src/providers/deepseek/index.test.ts +116 -1
  93. package/packages/model-runtime/src/providers/deepseek/index.ts +8 -3
  94. package/packages/model-runtime/src/providers/fireworksai/index.test.ts +264 -3
  95. package/packages/model-runtime/src/providers/fireworksai/index.ts +8 -3
  96. package/packages/model-runtime/src/providers/giteeai/index.test.ts +325 -3
  97. package/packages/model-runtime/src/providers/giteeai/index.ts +23 -6
  98. package/packages/model-runtime/src/providers/github/index.test.ts +532 -3
  99. package/packages/model-runtime/src/providers/github/index.ts +8 -3
  100. package/packages/model-runtime/src/providers/groq/index.test.ts +344 -31
  101. package/packages/model-runtime/src/providers/groq/index.ts +8 -3
  102. package/packages/model-runtime/src/providers/higress/index.test.ts +142 -0
  103. package/packages/model-runtime/src/providers/higress/index.ts +8 -3
  104. package/packages/model-runtime/src/providers/huggingface/index.test.ts +612 -1
  105. package/packages/model-runtime/src/providers/huggingface/index.ts +9 -4
  106. package/packages/model-runtime/src/providers/hunyuan/index.test.ts +365 -1
  107. package/packages/model-runtime/src/providers/hunyuan/index.ts +9 -3
  108. package/packages/model-runtime/src/providers/infiniai/index.test.ts +71 -0
  109. package/packages/model-runtime/src/providers/internlm/index.test.ts +369 -2
  110. package/packages/model-runtime/src/providers/internlm/index.ts +10 -5
  111. package/packages/model-runtime/src/providers/jina/index.test.ts +164 -3
  112. package/packages/model-runtime/src/providers/jina/index.ts +8 -3
  113. package/packages/model-runtime/src/providers/lmstudio/index.test.ts +182 -3
  114. package/packages/model-runtime/src/providers/lmstudio/index.ts +8 -3
  115. package/packages/model-runtime/src/providers/mistral/index.test.ts +779 -27
  116. package/packages/model-runtime/src/providers/mistral/index.ts +8 -3
  117. package/packages/model-runtime/src/providers/modelscope/index.test.ts +232 -1
  118. package/packages/model-runtime/src/providers/modelscope/index.ts +8 -3
  119. package/packages/model-runtime/src/providers/moonshot/index.test.ts +489 -2
  120. package/packages/model-runtime/src/providers/moonshot/index.ts +8 -3
  121. package/packages/model-runtime/src/providers/nebius/index.test.ts +381 -3
  122. package/packages/model-runtime/src/providers/nebius/index.ts +8 -3
  123. package/packages/model-runtime/src/providers/newapi/index.test.ts +667 -3
  124. package/packages/model-runtime/src/providers/newapi/index.ts +6 -3
  125. package/packages/model-runtime/src/providers/nvidia/index.test.ts +168 -1
  126. package/packages/model-runtime/src/providers/nvidia/index.ts +12 -7
  127. package/packages/model-runtime/src/providers/ollama/index.test.ts +797 -1
  128. package/packages/model-runtime/src/providers/ollama/index.ts +8 -0
  129. package/packages/model-runtime/src/providers/ollamacloud/index.test.ts +411 -0
  130. package/packages/model-runtime/src/providers/ollamacloud/index.ts +8 -3
  131. package/packages/model-runtime/src/providers/openai/index.test.ts +171 -2
  132. package/packages/model-runtime/src/providers/openai/index.ts +8 -3
  133. package/packages/model-runtime/src/providers/openrouter/index.test.ts +1647 -95
  134. package/packages/model-runtime/src/providers/openrouter/index.ts +12 -7
  135. package/packages/model-runtime/src/providers/qiniu/index.test.ts +294 -1
  136. package/packages/model-runtime/src/providers/qiniu/index.ts +8 -3
  137. package/packages/model-runtime/src/providers/search1api/index.test.ts +1131 -11
  138. package/packages/model-runtime/src/providers/search1api/index.ts +10 -4
  139. package/packages/model-runtime/src/providers/sensenova/index.test.ts +1069 -1
  140. package/packages/model-runtime/src/providers/sensenova/index.ts +8 -3
  141. package/packages/model-runtime/src/providers/siliconcloud/index.test.ts +196 -0
  142. package/packages/model-runtime/src/providers/siliconcloud/index.ts +8 -3
  143. package/packages/model-runtime/src/providers/spark/index.test.ts +293 -1
  144. package/packages/model-runtime/src/providers/spark/index.ts +8 -3
  145. package/packages/model-runtime/src/providers/stepfun/index.test.ts +322 -3
  146. package/packages/model-runtime/src/providers/stepfun/index.ts +8 -3
  147. package/packages/model-runtime/src/providers/tencentcloud/index.test.ts +182 -3
  148. package/packages/model-runtime/src/providers/tencentcloud/index.ts +8 -3
  149. package/packages/model-runtime/src/providers/togetherai/index.test.ts +359 -4
  150. package/packages/model-runtime/src/providers/togetherai/index.ts +12 -5
  151. package/packages/model-runtime/src/providers/v0/index.test.ts +341 -0
  152. package/packages/model-runtime/src/providers/v0/index.ts +20 -6
  153. package/packages/model-runtime/src/providers/vercelaigateway/index.test.ts +710 -0
  154. package/packages/model-runtime/src/providers/vercelaigateway/index.ts +19 -13
  155. package/packages/model-runtime/src/providers/vllm/index.test.ts +45 -1
  156. package/packages/model-runtime/src/providers/volcengine/index.test.ts +75 -0
  157. package/packages/model-runtime/src/providers/wenxin/index.test.ts +144 -1
  158. package/packages/model-runtime/src/providers/wenxin/index.ts +8 -3
  159. package/packages/model-runtime/src/providers/xai/index.test.ts +105 -1
  160. package/packages/model-runtime/src/providers/xinference/index.test.ts +70 -1
  161. package/packages/model-runtime/src/providers/zeroone/index.test.ts +327 -3
  162. package/packages/model-runtime/src/providers/zeroone/index.ts +23 -6
  163. package/packages/model-runtime/src/providers/zhipu/index.test.ts +908 -236
  164. package/packages/model-runtime/src/providers/zhipu/index.ts +8 -3
  165. package/packages/model-runtime/src/types/structureOutput.ts +5 -1
  166. package/packages/model-runtime/vitest.config.mts +7 -1
  167. package/packages/types/src/aiChat.ts +20 -2
  168. package/packages/types/src/serverConfig.ts +7 -1
  169. package/packages/types/src/tool/index.ts +1 -0
  170. package/packages/types/src/tool/tool.ts +33 -0
  171. package/packages/types/src/user/settings/image.ts +3 -0
  172. package/packages/types/src/user/settings/index.ts +3 -0
  173. package/src/app/[variants]/(main)/settings/_layout/SettingsContent.tsx +3 -0
  174. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +8 -3
  175. package/src/app/[variants]/(main)/settings/image/index.tsx +74 -0
  176. package/src/components/FormInput/FormSliderWithInput.tsx +40 -0
  177. package/src/components/FormInput/index.ts +1 -0
  178. package/src/envs/image.ts +27 -0
  179. package/src/features/Conversation/Messages/Assistant/index.tsx +1 -1
  180. package/src/features/Conversation/Messages/User/index.tsx +2 -2
  181. package/src/hooks/useFetchAiImageConfig.ts +12 -17
  182. package/src/locales/default/setting.ts +8 -0
  183. package/src/server/globalConfig/index.ts +5 -0
  184. package/src/server/routers/lambda/aiChat.ts +2 -0
  185. package/src/store/global/initialState.ts +1 -0
  186. package/src/store/image/slices/generationConfig/action.test.ts +17 -0
  187. package/src/store/image/slices/generationConfig/action.ts +18 -21
  188. package/src/store/image/slices/generationConfig/initialState.ts +3 -2
  189. package/src/store/user/slices/common/action.ts +1 -0
  190. package/src/store/user/slices/settings/selectors/settings.ts +3 -0
@@ -266,6 +266,13 @@
266
266
  "when": 1760086906862,
267
267
  "tag": "0037_add_user_memory",
268
268
  "breakpoints": true
269
+ },
270
+ {
271
+ "idx": 38,
272
+ "version": "7",
273
+ "when": 1760108430562,
274
+ "tag": "0038_add_image_user_settings",
275
+ "breakpoints": true
269
276
  }
270
277
  ],
271
278
  "version": "6"
@@ -676,5 +676,11 @@
676
676
  "bps": true,
677
677
  "folderMillis": 1760086906862,
678
678
  "hash": "4bdc6505797d7a33b622498c138cfd47f637239f6905e1c484cd01d9d5f21d6b"
679
+ },
680
+ {
681
+ "sql": ["ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"],
682
+ "bps": true,
683
+ "folderMillis": 1760108430562,
684
+ "hash": "ce09b301abb80f6563abc2f526bdd20b4f69bae430f09ba2179b9e3bfec43067"
679
685
  }
680
686
  ]
@@ -4,7 +4,6 @@ import { eq } from 'drizzle-orm';
4
4
  import type { AdapterAccount } from 'next-auth/adapters';
5
5
  import type { PartialDeep } from 'type-fest';
6
6
 
7
- import { LobeChatDatabase } from '../type';
8
7
  import { UserGuide, UserPreference } from '@/types/user';
9
8
  import { UserKeyVaults, UserSettings } from '@/types/user/settings';
10
9
  import { merge } from '@/utils/merge';
@@ -18,6 +17,7 @@ import {
18
17
  userSettings,
19
18
  users,
20
19
  } from '../schemas';
20
+ import { LobeChatDatabase } from '../type';
21
21
 
22
22
  type DecryptUserKeyVaults = (
23
23
  encryptKeyVaultsStr: string | null,
@@ -73,6 +73,7 @@ export class UserModel {
73
73
 
74
74
  settingsGeneral: userSettings.general,
75
75
  settingsHotkey: userSettings.hotkey,
76
+ settingsImage: userSettings.image,
76
77
  settingsKeyVaults: userSettings.keyVaults,
77
78
  settingsLanguageModel: userSettings.languageModel,
78
79
  settingsSystemAgent: userSettings.systemAgent,
@@ -103,6 +104,7 @@ export class UserModel {
103
104
  defaultAgent: state.settingsDefaultAgent || {},
104
105
  general: state.settingsGeneral || {},
105
106
  hotkey: state.settingsHotkey || {},
107
+ image: state.settingsImage || {},
106
108
  keyVaults: decryptKeyVaults,
107
109
  languageModel: state.settingsLanguageModel || {},
108
110
  systemAgent: state.settingsSystemAgent || {},
@@ -46,6 +46,7 @@ export const userSettings = pgTable('user_settings', {
46
46
  systemAgent: jsonb('system_agent'),
47
47
  defaultAgent: jsonb('default_agent'),
48
48
  tool: jsonb('tool'),
49
+ image: jsonb('image'),
49
50
  });
50
51
  export type UserSettingsItem = typeof userSettings.$inferSelect;
51
52
 
@@ -4,7 +4,6 @@ import { beforeEach, describe, expect, it } from 'vitest';
4
4
  import type { FileLoaderInterface } from '../../types';
5
5
  import { DocxLoader } from './index';
6
6
 
7
- // 确保你已经在 fixtures 目录下放置了 test.docx 文件
8
7
  const fixturePath = (filename: string) => path.join(__dirname, `./fixtures/${filename}`);
9
8
 
10
9
  let loader: FileLoaderInterface;
@@ -22,6 +22,36 @@ exports[`ExcelLoader > should aggregate content correctly (joining sheets) > agg
22
22
  </sheet>"
23
23
  `;
24
24
 
25
+ exports[`ExcelLoader > should handle Excel file with only headers > only_header_pages 1`] = `
26
+ [
27
+ {
28
+ "charCount": 124,
29
+ "lineCount": 3,
30
+ "metadata": {
31
+ "sheetName": "表1",
32
+ },
33
+ "pageContent": "| 表格 1 | __EMPTY | __EMPTY_1 | __EMPTY_2 | __EMPTY_3 |
34
+ | --- | --- | --- | --- | --- |
35
+ | header1 | header2 | header3 | | |",
36
+ },
37
+ {
38
+ "charCount": 231,
39
+ "lineCount": 8,
40
+ "metadata": {
41
+ "sheetName": "表2 - 表格 2",
42
+ },
43
+ "pageContent": "| 表格 2 | __EMPTY | __EMPTY_1 | __EMPTY_2 | __EMPTY_3 |
44
+ | --- | --- | --- | --- | --- |
45
+ | | 类别 A | 类别 B | | |
46
+ | 项目 1 | 5 | 7 | | |
47
+ | 项目 2 | 10 | 8 | | |
48
+ | 项目 3 | 9 | 15 | | |
49
+ | 项目 4 | 7 | 12 | | |
50
+ | 项目 5 | 16 | 21 | | |",
51
+ },
52
+ ]
53
+ `;
54
+
25
55
  exports[`ExcelLoader > should load pages correctly from an Excel file (one page per sheet) 1`] = `
26
56
  [
27
57
  {
@@ -44,4 +44,12 @@ describe('ExcelLoader', () => {
44
44
  expect(pages[0].pageContent).toBe('');
45
45
  expect(pages[0].metadata.error).toContain('Failed to load Excel file');
46
46
  });
47
+
48
+ it('should handle Excel file with only headers', async () => {
49
+ const onlyHeaderFile = fixturePath('only-header.xlsx');
50
+ const pages = await loader.loadPages(onlyHeaderFile);
51
+ expect(pages.length).toBeGreaterThan(0);
52
+ expect(pages[0].pageContent).toBeTruthy(); // 应该包含表头内容
53
+ expect(pages).toMatchSnapshot('only_header_pages');
54
+ });
47
55
  });
@@ -44,4 +44,29 @@ describe('PptxLoader', () => {
44
44
  expect(pages[0].pageContent).toBe('');
45
45
  expect(pages[0].metadata.error).toContain('Failed to load or process PPTX file:'); // Update error message check
46
46
  });
47
+
48
+ it('should handle corrupted slide XML', async () => {
49
+ const corruptedFile = fixturePath('corrupted-slides.pptx');
50
+ const pages = await loader.loadPages(corruptedFile);
51
+ expect(pages).toHaveLength(1);
52
+ expect(pages[0].pageContent).toBe('');
53
+ expect(pages[0].metadata.error).toContain('All slides failed to parse correctly');
54
+ });
55
+
56
+ it('should handle aggregateContent with all error pages', async () => {
57
+ const corruptedFile = fixturePath('corrupted-slides.pptx');
58
+ const pages = await loader.loadPages(corruptedFile);
59
+ const content = await loader.aggregateContent(pages);
60
+ expect(content).toBe(''); // 所有页面都是错误页面时返回空字符串
61
+ });
62
+
63
+ it('should handle empty PPTX file with no slides', async () => {
64
+ const emptyFile = fixturePath('empty-slides.pptx');
65
+ const pages = await loader.loadPages(emptyFile);
66
+ expect(pages).toHaveLength(1);
67
+ expect(pages[0].pageContent).toBe('');
68
+ expect(pages[0].metadata.error).toContain(
69
+ 'No slides found. The PPTX file might be empty, corrupted, or does not contain standard slide XMLs.',
70
+ );
71
+ });
47
72
  });
@@ -31,6 +31,20 @@ describe('parser-utils', () => {
31
31
  );
32
32
  });
33
33
 
34
+ it('should handle corrupted file error with Buffer input', async () => {
35
+ vi.doMock('yauzl', () => ({
36
+ default: {
37
+ fromBuffer: (_buf: Buffer, _opts: any, cb: (err: any) => void) => {
38
+ cb(new Error('corrupted'));
39
+ },
40
+ },
41
+ }));
42
+
43
+ const { extractFiles: mockedExtractFiles } = await import('./parser-utils');
44
+
45
+ await expect(mockedExtractFiles(Buffer.from('corrupted'), () => true)).rejects.toThrow();
46
+ });
47
+
34
48
  it('should read entries via yauzl.fromBuffer and filter matches', async () => {
35
49
  // Arrange: build a fake zipfile object with two file entries and one directory
36
50
  const entryHandlers: Record<string, (cb: () => void) => void> = {};
@@ -151,5 +165,146 @@ describe('parser-utils', () => {
151
165
  const files = await mockedExtractFiles('/tmp/file.zip', (name) => name === 'keep.txt');
152
166
  expect(files).toEqual([{ path: 'keep.txt', content: 'A' }]);
153
167
  });
168
+
169
+ it('should handle openReadStream error', async () => {
170
+ const listeners: Record<string, Function[]> = { entry: [], end: [], error: [] };
171
+ const emit = (name: string, payload?: any) =>
172
+ (listeners[name] || []).forEach((fn) => fn(payload));
173
+
174
+ const fakeZipfile = {
175
+ readEntry: vi.fn().mockImplementation(() => {
176
+ queueMicrotask(() => emit('entry', { fileName: 'test.txt' }));
177
+ }),
178
+ openReadStream: vi.fn((entry: any, cb: (err: any, stream?: any) => void) => {
179
+ cb(new Error('Failed to open stream'));
180
+ }),
181
+ on: vi.fn((evt: string, handler: Function) => {
182
+ listeners[evt] = listeners[evt] || [];
183
+ listeners[evt].push(handler);
184
+ }),
185
+ close: vi.fn(),
186
+ } as any;
187
+
188
+ vi.doMock('yauzl', () => ({
189
+ default: {
190
+ fromBuffer: (_buf: Buffer, _opts: any, cb: (err: any, zf?: any) => void) =>
191
+ cb(null, fakeZipfile),
192
+ },
193
+ }));
194
+
195
+ const { extractFiles: mockedExtractFiles } = await import('./parser-utils');
196
+
197
+ await expect(mockedExtractFiles(Buffer.from('zip'), () => true)).rejects.toThrow(
198
+ 'Failed to open stream',
199
+ );
200
+ });
201
+
202
+ it('should handle null readStream', async () => {
203
+ const listeners: Record<string, Function[]> = { entry: [], end: [], error: [] };
204
+ const emit = (name: string, payload?: any) =>
205
+ (listeners[name] || []).forEach((fn) => fn(payload));
206
+
207
+ const fakeZipfile = {
208
+ readEntry: vi.fn().mockImplementation(() => {
209
+ queueMicrotask(() => emit('entry', { fileName: 'test.txt' }));
210
+ }),
211
+ openReadStream: vi.fn((entry: any, cb: (err: any, stream?: any) => void) => {
212
+ cb(null, null); // readStream is null
213
+ }),
214
+ on: vi.fn((evt: string, handler: Function) => {
215
+ listeners[evt] = listeners[evt] || [];
216
+ listeners[evt].push(handler);
217
+ }),
218
+ close: vi.fn(),
219
+ } as any;
220
+
221
+ vi.doMock('yauzl', () => ({
222
+ default: {
223
+ fromBuffer: (_buf: Buffer, _opts: any, cb: (err: any, zf?: any) => void) =>
224
+ cb(null, fakeZipfile),
225
+ },
226
+ }));
227
+
228
+ const { extractFiles: mockedExtractFiles } = await import('./parser-utils');
229
+
230
+ await expect(mockedExtractFiles(Buffer.from('zip'), () => true)).rejects.toThrow(
231
+ 'Could not open read stream',
232
+ );
233
+ });
234
+
235
+ it('should handle readStream error', async () => {
236
+ const listeners: Record<string, Function[]> = { entry: [], end: [], error: [] };
237
+ const streamListeners: Record<string, Function[]> = { error: [] };
238
+ const emit = (name: string, payload?: any) =>
239
+ (listeners[name] || []).forEach((fn) => fn(payload));
240
+
241
+ const fakeZipfile = {
242
+ readEntry: vi.fn().mockImplementation(() => {
243
+ queueMicrotask(() => emit('entry', { fileName: 'test.txt' }));
244
+ }),
245
+ openReadStream: vi.fn((entry: any, cb: (err: any, stream?: any) => void) => {
246
+ const stream = {
247
+ pipe: vi.fn().mockReturnThis(),
248
+ on: vi.fn((evt: string, handler: Function) => {
249
+ streamListeners[evt] = streamListeners[evt] || [];
250
+ streamListeners[evt].push(handler);
251
+ // Immediately trigger error
252
+ if (evt === 'error') {
253
+ queueMicrotask(() => handler(new Error('Stream error')));
254
+ }
255
+ }),
256
+ };
257
+ cb(null, stream);
258
+ }),
259
+ on: vi.fn((evt: string, handler: Function) => {
260
+ listeners[evt] = listeners[evt] || [];
261
+ listeners[evt].push(handler);
262
+ }),
263
+ close: vi.fn(),
264
+ } as any;
265
+
266
+ vi.doMock('yauzl', () => ({
267
+ default: {
268
+ fromBuffer: (_buf: Buffer, _opts: any, cb: (err: any, zf?: any) => void) =>
269
+ cb(null, fakeZipfile),
270
+ },
271
+ }));
272
+
273
+ const { extractFiles: mockedExtractFiles } = await import('./parser-utils');
274
+
275
+ await expect(mockedExtractFiles(Buffer.from('zip'), () => true)).rejects.toThrow(
276
+ 'Stream error',
277
+ );
278
+ });
279
+
280
+ it('should handle zipfile error', async () => {
281
+ const listeners: Record<string, Function[]> = { entry: [], end: [], error: [] };
282
+ const emit = (name: string, payload?: any) =>
283
+ (listeners[name] || []).forEach((fn) => fn(payload));
284
+
285
+ const fakeZipfile = {
286
+ readEntry: vi.fn().mockImplementation(() => {
287
+ queueMicrotask(() => emit('error', new Error('Zipfile error')));
288
+ }),
289
+ on: vi.fn((evt: string, handler: Function) => {
290
+ listeners[evt] = listeners[evt] || [];
291
+ listeners[evt].push(handler);
292
+ }),
293
+ close: vi.fn(),
294
+ } as any;
295
+
296
+ vi.doMock('yauzl', () => ({
297
+ default: {
298
+ fromBuffer: (_buf: Buffer, _opts: any, cb: (err: any, zf?: any) => void) =>
299
+ cb(null, fakeZipfile),
300
+ },
301
+ }));
302
+
303
+ const { extractFiles: mockedExtractFiles } = await import('./parser-utils');
304
+
305
+ await expect(mockedExtractFiles(Buffer.from('zip'), () => true)).rejects.toThrow(
306
+ 'Zipfile error',
307
+ );
308
+ });
154
309
  });
155
310
  });
@@ -3,6 +3,14 @@ import { defineConfig } from 'vitest/config';
3
3
  export default defineConfig({
4
4
  test: {
5
5
  coverage: {
6
+ exclude: [
7
+ '**/types.ts',
8
+ '**/types/**',
9
+ '**/*.d.ts',
10
+ '**/test/setup.ts',
11
+ '**/vitest.config.*',
12
+ '**/node_modules/**',
13
+ ],
6
14
  reporter: ['text', 'json', 'lcov', 'text-summary'],
7
15
  },
8
16
  environment: 'happy-dom',
@@ -0,0 +1,5 @@
1
+ # Model Runtime Package
2
+
3
+ ## Testing
4
+
5
+ For detailed test coverage status, testing strategies, and implementation guidelines, see [Test Coverage Documentation](./docs/test-coverage.md).