@code-rag/core 0.1.0 → 0.1.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 (205) hide show
  1. package/package.json +4 -4
  2. package/dist/auth/audit-log.js.map +0 -1
  3. package/dist/auth/audit-log.test.d.ts +0 -1
  4. package/dist/auth/audit-log.test.js +0 -261
  5. package/dist/auth/audit-log.test.js.map +0 -1
  6. package/dist/auth/index.js.map +0 -1
  7. package/dist/auth/oidc-provider.js.map +0 -1
  8. package/dist/auth/oidc-provider.test.d.ts +0 -1
  9. package/dist/auth/oidc-provider.test.js +0 -520
  10. package/dist/auth/oidc-provider.test.js.map +0 -1
  11. package/dist/auth/rbac.js.map +0 -1
  12. package/dist/auth/rbac.test.d.ts +0 -1
  13. package/dist/auth/rbac.test.js +0 -224
  14. package/dist/auth/rbac.test.js.map +0 -1
  15. package/dist/auth/saml-provider.js.map +0 -1
  16. package/dist/auth/saml-provider.test.d.ts +0 -1
  17. package/dist/auth/saml-provider.test.js +0 -422
  18. package/dist/auth/saml-provider.test.js.map +0 -1
  19. package/dist/auth/types.js.map +0 -1
  20. package/dist/auth/types.test.d.ts +0 -1
  21. package/dist/auth/types.test.js +0 -147
  22. package/dist/auth/types.test.js.map +0 -1
  23. package/dist/backlog/ab-reference-scanner.js.map +0 -1
  24. package/dist/backlog/ab-reference-scanner.test.d.ts +0 -1
  25. package/dist/backlog/ab-reference-scanner.test.js +0 -83
  26. package/dist/backlog/ab-reference-scanner.test.js.map +0 -1
  27. package/dist/backlog/azure-devops-provider.js.map +0 -1
  28. package/dist/backlog/backlog-provider.js.map +0 -1
  29. package/dist/backlog/backlog-provider.test.d.ts +0 -1
  30. package/dist/backlog/backlog-provider.test.js +0 -426
  31. package/dist/backlog/backlog-provider.test.js.map +0 -1
  32. package/dist/backlog/clickup-provider.js.map +0 -1
  33. package/dist/backlog/clickup-provider.test.d.ts +0 -1
  34. package/dist/backlog/clickup-provider.test.js +0 -426
  35. package/dist/backlog/clickup-provider.test.js.map +0 -1
  36. package/dist/backlog/clickup-reference-scanner.js.map +0 -1
  37. package/dist/backlog/clickup-reference-scanner.test.d.ts +0 -1
  38. package/dist/backlog/clickup-reference-scanner.test.js +0 -92
  39. package/dist/backlog/clickup-reference-scanner.test.js.map +0 -1
  40. package/dist/backlog/code-linker.js.map +0 -1
  41. package/dist/backlog/code-linker.test.d.ts +0 -1
  42. package/dist/backlog/code-linker.test.js +0 -325
  43. package/dist/backlog/code-linker.test.js.map +0 -1
  44. package/dist/backlog/index.js.map +0 -1
  45. package/dist/backlog/jira-provider.js.map +0 -1
  46. package/dist/backlog/jira-provider.test.d.ts +0 -1
  47. package/dist/backlog/jira-provider.test.js +0 -449
  48. package/dist/backlog/jira-provider.test.js.map +0 -1
  49. package/dist/backlog/jira-reference-scanner.js.map +0 -1
  50. package/dist/backlog/jira-reference-scanner.test.d.ts +0 -1
  51. package/dist/backlog/jira-reference-scanner.test.js +0 -127
  52. package/dist/backlog/jira-reference-scanner.test.js.map +0 -1
  53. package/dist/backlog/types.js.map +0 -1
  54. package/dist/chunker/ast-chunker.js.map +0 -1
  55. package/dist/chunker/ast-chunker.test.d.ts +0 -1
  56. package/dist/chunker/ast-chunker.test.js +0 -391
  57. package/dist/chunker/ast-chunker.test.js.map +0 -1
  58. package/dist/chunker/chunker.js.map +0 -1
  59. package/dist/chunker/index.js.map +0 -1
  60. package/dist/config/config-parser.js.map +0 -1
  61. package/dist/config/config-parser.test.d.ts +0 -1
  62. package/dist/config/config-parser.test.js +0 -699
  63. package/dist/config/config-parser.test.js.map +0 -1
  64. package/dist/docs/confluence-provider.js.map +0 -1
  65. package/dist/docs/confluence-provider.test.d.ts +0 -1
  66. package/dist/docs/confluence-provider.test.js +0 -765
  67. package/dist/docs/confluence-provider.test.js.map +0 -1
  68. package/dist/docs/index.js.map +0 -1
  69. package/dist/docs/sharepoint-provider.js.map +0 -1
  70. package/dist/docs/sharepoint-provider.test.d.ts +0 -1
  71. package/dist/docs/sharepoint-provider.test.js +0 -873
  72. package/dist/docs/sharepoint-provider.test.js.map +0 -1
  73. package/dist/embedding/bm25-index.js.map +0 -1
  74. package/dist/embedding/bm25-index.test.d.ts +0 -1
  75. package/dist/embedding/bm25-index.test.js +0 -289
  76. package/dist/embedding/bm25-index.test.js.map +0 -1
  77. package/dist/embedding/hybrid-search.js.map +0 -1
  78. package/dist/embedding/hybrid-search.test.d.ts +0 -1
  79. package/dist/embedding/hybrid-search.test.js +0 -266
  80. package/dist/embedding/hybrid-search.test.js.map +0 -1
  81. package/dist/embedding/index.js.map +0 -1
  82. package/dist/embedding/lancedb-store.js.map +0 -1
  83. package/dist/embedding/lancedb-store.test.d.ts +0 -1
  84. package/dist/embedding/lancedb-store.test.js +0 -268
  85. package/dist/embedding/lancedb-store.test.js.map +0 -1
  86. package/dist/embedding/model-lifecycle-manager.js.map +0 -1
  87. package/dist/embedding/model-lifecycle-manager.test.d.ts +0 -1
  88. package/dist/embedding/model-lifecycle-manager.test.js +0 -642
  89. package/dist/embedding/model-lifecycle-manager.test.js.map +0 -1
  90. package/dist/embedding/ollama-embedding-provider.js.map +0 -1
  91. package/dist/embedding/ollama-embedding-provider.test.d.ts +0 -1
  92. package/dist/embedding/ollama-embedding-provider.test.js +0 -198
  93. package/dist/embedding/ollama-embedding-provider.test.js.map +0 -1
  94. package/dist/embedding/openai-compatible-embedding-provider.js.map +0 -1
  95. package/dist/embedding/openai-compatible-embedding-provider.test.d.ts +0 -1
  96. package/dist/embedding/openai-compatible-embedding-provider.test.js +0 -456
  97. package/dist/embedding/openai-compatible-embedding-provider.test.js.map +0 -1
  98. package/dist/embedding/qdrant-store.js.map +0 -1
  99. package/dist/embedding/qdrant-store.test.d.ts +0 -1
  100. package/dist/embedding/qdrant-store.test.js +0 -359
  101. package/dist/embedding/qdrant-store.test.js.map +0 -1
  102. package/dist/enrichment/index.js.map +0 -1
  103. package/dist/enrichment/nl-enricher.js.map +0 -1
  104. package/dist/enrichment/nl-enricher.test.d.ts +0 -1
  105. package/dist/enrichment/nl-enricher.test.js +0 -154
  106. package/dist/enrichment/nl-enricher.test.js.map +0 -1
  107. package/dist/enrichment/ollama-client.js.map +0 -1
  108. package/dist/enrichment/ollama-client.test.d.ts +0 -1
  109. package/dist/enrichment/ollama-client.test.js +0 -129
  110. package/dist/enrichment/ollama-client.test.js.map +0 -1
  111. package/dist/git/git-client.js.map +0 -1
  112. package/dist/git/git-client.test.d.ts +0 -1
  113. package/dist/git/git-client.test.js +0 -200
  114. package/dist/git/git-client.test.js.map +0 -1
  115. package/dist/git/ignore-filter.js.map +0 -1
  116. package/dist/git/ignore-filter.test.d.ts +0 -1
  117. package/dist/git/ignore-filter.test.js +0 -87
  118. package/dist/git/ignore-filter.test.js.map +0 -1
  119. package/dist/git/index.js.map +0 -1
  120. package/dist/git/simple-git-client.js.map +0 -1
  121. package/dist/graph/cross-repo-resolver.js.map +0 -1
  122. package/dist/graph/cross-repo-resolver.test.d.ts +0 -1
  123. package/dist/graph/cross-repo-resolver.test.js +0 -548
  124. package/dist/graph/cross-repo-resolver.test.js.map +0 -1
  125. package/dist/graph/dependency-graph.js.map +0 -1
  126. package/dist/graph/dependency-graph.test.d.ts +0 -1
  127. package/dist/graph/dependency-graph.test.js +0 -276
  128. package/dist/graph/dependency-graph.test.js.map +0 -1
  129. package/dist/graph/graph-builder.js.map +0 -1
  130. package/dist/graph/graph-builder.test.d.ts +0 -1
  131. package/dist/graph/graph-builder.test.js +0 -178
  132. package/dist/graph/graph-builder.test.js.map +0 -1
  133. package/dist/graph/import-resolver.js.map +0 -1
  134. package/dist/graph/import-resolver.test.d.ts +0 -1
  135. package/dist/graph/import-resolver.test.js +0 -282
  136. package/dist/graph/import-resolver.test.js.map +0 -1
  137. package/dist/graph/index.js.map +0 -1
  138. package/dist/index.js.map +0 -1
  139. package/dist/indexer/file-scanner.js.map +0 -1
  140. package/dist/indexer/file-scanner.test.d.ts +0 -1
  141. package/dist/indexer/file-scanner.test.js +0 -110
  142. package/dist/indexer/file-scanner.test.js.map +0 -1
  143. package/dist/indexer/incremental-indexer.js.map +0 -1
  144. package/dist/indexer/incremental-indexer.test.d.ts +0 -1
  145. package/dist/indexer/incremental-indexer.test.js +0 -266
  146. package/dist/indexer/incremental-indexer.test.js.map +0 -1
  147. package/dist/indexer/index-check.js.map +0 -1
  148. package/dist/indexer/index-check.test.d.ts +0 -1
  149. package/dist/indexer/index-check.test.js +0 -100
  150. package/dist/indexer/index-check.test.js.map +0 -1
  151. package/dist/indexer/index-state.js.map +0 -1
  152. package/dist/indexer/index-state.test.d.ts +0 -1
  153. package/dist/indexer/index-state.test.js +0 -140
  154. package/dist/indexer/index-state.test.js.map +0 -1
  155. package/dist/indexer/index.js.map +0 -1
  156. package/dist/indexer/multi-repo-indexer.js.map +0 -1
  157. package/dist/indexer/multi-repo-indexer.test.d.ts +0 -1
  158. package/dist/indexer/multi-repo-indexer.test.js +0 -238
  159. package/dist/indexer/multi-repo-indexer.test.js.map +0 -1
  160. package/dist/parser/index.js.map +0 -1
  161. package/dist/parser/language-registry.js.map +0 -1
  162. package/dist/parser/language-registry.test.d.ts +0 -1
  163. package/dist/parser/language-registry.test.js +0 -225
  164. package/dist/parser/language-registry.test.js.map +0 -1
  165. package/dist/parser/markdown-parser.js.map +0 -1
  166. package/dist/parser/markdown-parser.test.d.ts +0 -1
  167. package/dist/parser/markdown-parser.test.js +0 -600
  168. package/dist/parser/markdown-parser.test.js.map +0 -1
  169. package/dist/parser/tree-sitter-parser.js.map +0 -1
  170. package/dist/retrieval/context-expander.js.map +0 -1
  171. package/dist/retrieval/context-expander.test.d.ts +0 -1
  172. package/dist/retrieval/context-expander.test.js +0 -339
  173. package/dist/retrieval/context-expander.test.js.map +0 -1
  174. package/dist/retrieval/cross-encoder-reranker.js.map +0 -1
  175. package/dist/retrieval/cross-encoder-reranker.test.d.ts +0 -1
  176. package/dist/retrieval/cross-encoder-reranker.test.js +0 -305
  177. package/dist/retrieval/cross-encoder-reranker.test.js.map +0 -1
  178. package/dist/retrieval/index.js.map +0 -1
  179. package/dist/retrieval/query-analyzer.js.map +0 -1
  180. package/dist/retrieval/query-analyzer.test.d.ts +0 -1
  181. package/dist/retrieval/query-analyzer.test.js +0 -236
  182. package/dist/retrieval/query-analyzer.test.js.map +0 -1
  183. package/dist/retrieval/token-budget.js.map +0 -1
  184. package/dist/retrieval/token-budget.test.d.ts +0 -1
  185. package/dist/retrieval/token-budget.test.js +0 -404
  186. package/dist/retrieval/token-budget.test.js.map +0 -1
  187. package/dist/storage/azure-blob-provider.js.map +0 -1
  188. package/dist/storage/azure-blob-provider.test.d.ts +0 -1
  189. package/dist/storage/azure-blob-provider.test.js +0 -250
  190. package/dist/storage/azure-blob-provider.test.js.map +0 -1
  191. package/dist/storage/gcs-provider.js.map +0 -1
  192. package/dist/storage/gcs-provider.test.d.ts +0 -1
  193. package/dist/storage/gcs-provider.test.js +0 -299
  194. package/dist/storage/gcs-provider.test.js.map +0 -1
  195. package/dist/storage/index.js.map +0 -1
  196. package/dist/storage/s3-provider.js.map +0 -1
  197. package/dist/storage/s3-provider.test.d.ts +0 -1
  198. package/dist/storage/s3-provider.test.js +0 -329
  199. package/dist/storage/s3-provider.test.js.map +0 -1
  200. package/dist/storage/types.js.map +0 -1
  201. package/dist/types/chunk.js.map +0 -1
  202. package/dist/types/config.js.map +0 -1
  203. package/dist/types/index.js.map +0 -1
  204. package/dist/types/provider.js.map +0 -1
  205. package/dist/types/search.js.map +0 -1
@@ -1,873 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { SharePointProvider, SharePointError, extractTextFromDocx, extractTextFromPdf, } from './sharepoint-provider.js';
3
- // ---------------------------------------------------------------------------
4
- // Helpers
5
- // ---------------------------------------------------------------------------
6
- function createMockResponse(body, status = 200, statusText = 'OK') {
7
- return {
8
- ok: status >= 200 && status < 300,
9
- status,
10
- statusText,
11
- json: () => Promise.resolve(body),
12
- arrayBuffer: () => Promise.resolve(body),
13
- headers: new Headers(),
14
- redirected: false,
15
- type: 'basic',
16
- url: '',
17
- clone: () => createMockResponse(body, status, statusText),
18
- body: null,
19
- bodyUsed: false,
20
- blob: () => Promise.resolve(new Blob()),
21
- formData: () => Promise.resolve(new FormData()),
22
- text: () => Promise.resolve(JSON.stringify(body)),
23
- bytes: () => Promise.resolve(new Uint8Array()),
24
- };
25
- }
26
- function createTokenResponse(expiresIn = 3600) {
27
- return {
28
- access_token: 'mock-access-token-abc123',
29
- token_type: 'Bearer',
30
- expires_in: expiresIn,
31
- };
32
- }
33
- function createSitePage(overrides) {
34
- return {
35
- id: 'page-001',
36
- title: 'Engineering Handbook',
37
- webUrl: 'https://contoso.sharepoint.com/sites/eng/SitePages/handbook.aspx',
38
- lastModifiedDateTime: '2026-02-20T10:00:00Z',
39
- contentType: { name: 'Site Page' },
40
- ...(overrides ?? {}),
41
- };
42
- }
43
- function createDriveItem(overrides) {
44
- return {
45
- id: 'item-001',
46
- name: 'Architecture.docx',
47
- file: {
48
- mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
49
- },
50
- size: 45000,
51
- webUrl: 'https://contoso.sharepoint.com/sites/eng/Shared%20Documents/Architecture.docx',
52
- lastModifiedDateTime: '2026-02-18T14:30:00Z',
53
- parentReference: {
54
- driveId: 'drive-001',
55
- name: 'Documents',
56
- },
57
- ...(overrides ?? {}),
58
- };
59
- }
60
- function createDrive(overrides) {
61
- return {
62
- id: 'drive-001',
63
- name: 'Documents',
64
- webUrl: 'https://contoso.sharepoint.com/sites/eng/Shared%20Documents',
65
- ...(overrides ?? {}),
66
- };
67
- }
68
- const VALID_CONFIG = {
69
- tenantId: 'tenant-abc-123',
70
- clientId: 'client-def-456',
71
- clientSecret: 'secret-ghi-789',
72
- siteIds: ['site-001', 'site-002'],
73
- libraryNames: ['Documents'],
74
- maxPages: 25,
75
- };
76
- // ---------------------------------------------------------------------------
77
- // Tests
78
- // ---------------------------------------------------------------------------
79
- describe('SharePointProvider', () => {
80
- let provider;
81
- let fetchSpy;
82
- beforeEach(() => {
83
- provider = new SharePointProvider();
84
- fetchSpy = vi.fn();
85
- vi.stubGlobal('fetch', fetchSpy);
86
- });
87
- afterEach(() => {
88
- vi.restoreAllMocks();
89
- });
90
- // --- name ---
91
- it('should have name set to sharepoint', () => {
92
- expect(provider.name).toBe('sharepoint');
93
- });
94
- // --- initialize ---
95
- describe('initialize', () => {
96
- it('should return ok on successful token acquisition', async () => {
97
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
98
- const result = await provider.initialize(VALID_CONFIG);
99
- expect(result.isOk()).toBe(true);
100
- expect(fetchSpy).toHaveBeenCalledOnce();
101
- const url = fetchSpy.mock.calls[0][0];
102
- expect(url).toContain('login.microsoftonline.com');
103
- expect(url).toContain(VALID_CONFIG.tenantId);
104
- expect(url).toContain('/oauth2/v2.0/token');
105
- });
106
- it('should send correct OAuth2 client credentials request', async () => {
107
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
108
- await provider.initialize(VALID_CONFIG);
109
- const callOptions = fetchSpy.mock.calls[0][1];
110
- expect(callOptions.method).toBe('POST');
111
- const contentType = callOptions.headers['Content-Type'];
112
- expect(contentType).toBe('application/x-www-form-urlencoded');
113
- const body = callOptions.body;
114
- expect(body).toContain('client_id=client-def-456');
115
- expect(body).toContain('client_secret=secret-ghi-789');
116
- expect(body).toContain('grant_type=client_credentials');
117
- expect(body).toContain('scope=https%3A%2F%2Fgraph.microsoft.com%2F.default');
118
- });
119
- it('should return err when tenantId is missing', async () => {
120
- const result = await provider.initialize({
121
- clientId: 'abc',
122
- clientSecret: 'def',
123
- });
124
- expect(result.isErr()).toBe(true);
125
- if (result.isErr()) {
126
- expect(result.error).toBeInstanceOf(SharePointError);
127
- expect(result.error.message).toContain('tenantId');
128
- }
129
- });
130
- it('should return err when clientId is missing', async () => {
131
- const result = await provider.initialize({
132
- tenantId: 'abc',
133
- clientSecret: 'def',
134
- });
135
- expect(result.isErr()).toBe(true);
136
- if (result.isErr()) {
137
- expect(result.error).toBeInstanceOf(SharePointError);
138
- expect(result.error.message).toContain('clientId');
139
- }
140
- });
141
- it('should return err when clientSecret is missing', async () => {
142
- const result = await provider.initialize({
143
- tenantId: 'abc',
144
- clientId: 'def',
145
- });
146
- expect(result.isErr()).toBe(true);
147
- if (result.isErr()) {
148
- expect(result.error).toBeInstanceOf(SharePointError);
149
- expect(result.error.message).toContain('clientSecret');
150
- }
151
- });
152
- it('should return err when token acquisition fails with HTTP error', async () => {
153
- fetchSpy.mockResolvedValueOnce(createMockResponse({ error: 'invalid_client' }, 401, 'Unauthorized'));
154
- const result = await provider.initialize(VALID_CONFIG);
155
- expect(result.isErr()).toBe(true);
156
- if (result.isErr()) {
157
- expect(result.error).toBeInstanceOf(SharePointError);
158
- expect(result.error.message).toContain('401');
159
- }
160
- });
161
- it('should return err when token acquisition throws a network error', async () => {
162
- fetchSpy.mockRejectedValueOnce(new Error('ECONNREFUSED'));
163
- const result = await provider.initialize(VALID_CONFIG);
164
- expect(result.isErr()).toBe(true);
165
- if (result.isErr()) {
166
- expect(result.error.message).toContain('ECONNREFUSED');
167
- }
168
- });
169
- it('should use default maxPages when not specified', async () => {
170
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
171
- const result = await provider.initialize({
172
- tenantId: 'abc',
173
- clientId: 'def',
174
- clientSecret: 'ghi',
175
- });
176
- expect(result.isOk()).toBe(true);
177
- });
178
- it('should filter out non-string siteIds', async () => {
179
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
180
- const result = await provider.initialize({
181
- ...VALID_CONFIG,
182
- siteIds: ['valid-site', 123, null, 'another-site'],
183
- });
184
- expect(result.isOk()).toBe(true);
185
- });
186
- });
187
- // --- fetchPages ---
188
- describe('fetchPages', () => {
189
- async function initializeProvider() {
190
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
191
- await provider.initialize(VALID_CONFIG);
192
- }
193
- it('should fetch pages from specified sites', async () => {
194
- await initializeProvider();
195
- // Pages response
196
- fetchSpy.mockResolvedValueOnce(createMockResponse({
197
- value: [createSitePage()],
198
- }));
199
- // Page content (web parts)
200
- fetchSpy.mockResolvedValueOnce(createMockResponse({
201
- value: [{ innerHtml: '<p>Welcome to the Engineering Handbook.</p>' }],
202
- }));
203
- const result = await provider.fetchPages(['site-001']);
204
- expect(result.isOk()).toBe(true);
205
- if (result.isOk()) {
206
- expect(result.value).toHaveLength(1);
207
- const page = result.value[0];
208
- expect(page.id).toBe('page-001');
209
- expect(page.title).toBe('Engineering Handbook');
210
- expect(page.type).toBe('page');
211
- expect(page.siteId).toBe('site-001');
212
- expect(page.plainText).toContain('Welcome to the Engineering Handbook');
213
- }
214
- });
215
- it('should handle pagination with @odata.nextLink', async () => {
216
- await initializeProvider();
217
- // First page of results
218
- fetchSpy.mockResolvedValueOnce(createMockResponse({
219
- value: [createSitePage({ id: 'page-1', title: 'Page 1' })],
220
- '@odata.nextLink': 'https://graph.microsoft.com/v1.0/sites/site-001/pages?$skiptoken=abc',
221
- }));
222
- // Second page of results
223
- fetchSpy.mockResolvedValueOnce(createMockResponse({
224
- value: [createSitePage({ id: 'page-2', title: 'Page 2' })],
225
- }));
226
- // Content fetch for page-1
227
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
228
- // Content fetch for page-2
229
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
230
- const result = await provider.fetchPages(['site-001']);
231
- expect(result.isOk()).toBe(true);
232
- if (result.isOk()) {
233
- expect(result.value).toHaveLength(2);
234
- expect(result.value[0].id).toBe('page-1');
235
- expect(result.value[1].id).toBe('page-2');
236
- }
237
- });
238
- it('should return err when no site IDs are provided and none configured', async () => {
239
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
240
- await provider.initialize({
241
- tenantId: 'abc',
242
- clientId: 'def',
243
- clientSecret: 'ghi',
244
- });
245
- const result = await provider.fetchPages();
246
- expect(result.isErr()).toBe(true);
247
- if (result.isErr()) {
248
- expect(result.error.message).toContain('No site IDs');
249
- }
250
- });
251
- it('should use configured siteIds when none passed explicitly', async () => {
252
- await initializeProvider();
253
- // Pages for site-001
254
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createSitePage()] }));
255
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
256
- // Pages for site-002
257
- fetchSpy.mockResolvedValueOnce(createMockResponse({
258
- value: [createSitePage({ id: 'page-002', title: 'Site 2 Page' })],
259
- }));
260
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
261
- const result = await provider.fetchPages();
262
- expect(result.isOk()).toBe(true);
263
- if (result.isOk()) {
264
- expect(result.value).toHaveLength(2);
265
- }
266
- });
267
- it('should return err on Graph API failure', async () => {
268
- await initializeProvider();
269
- fetchSpy.mockResolvedValueOnce(createMockResponse({ error: { message: 'Forbidden' } }, 403, 'Forbidden'));
270
- const result = await provider.fetchPages(['site-001']);
271
- expect(result.isErr()).toBe(true);
272
- if (result.isErr()) {
273
- expect(result.error.message).toContain('403');
274
- }
275
- });
276
- it('should return empty array when no pages exist', async () => {
277
- await initializeProvider();
278
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
279
- const result = await provider.fetchPages(['site-001']);
280
- expect(result.isOk()).toBe(true);
281
- if (result.isOk()) {
282
- expect(result.value).toEqual([]);
283
- }
284
- });
285
- it('should handle page content fetch failure gracefully', async () => {
286
- await initializeProvider();
287
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createSitePage()] }));
288
- // Content fetch fails
289
- fetchSpy.mockResolvedValueOnce(createMockResponse({}, 500, 'Internal Server Error'));
290
- const result = await provider.fetchPages(['site-001']);
291
- expect(result.isOk()).toBe(true);
292
- if (result.isOk()) {
293
- expect(result.value).toHaveLength(1);
294
- expect(result.value[0].plainText).toBe('');
295
- }
296
- });
297
- it('should combine web part HTML content', async () => {
298
- await initializeProvider();
299
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createSitePage()] }));
300
- fetchSpy.mockResolvedValueOnce(createMockResponse({
301
- value: [
302
- { innerHtml: '<p>Part 1</p>' },
303
- { data: { innerHTML: '<p>Part 2</p>' } },
304
- ],
305
- }));
306
- const result = await provider.fetchPages(['site-001']);
307
- expect(result.isOk()).toBe(true);
308
- if (result.isOk()) {
309
- expect(result.value[0].plainText).toContain('Part 1');
310
- expect(result.value[0].plainText).toContain('Part 2');
311
- }
312
- });
313
- it('should return err on network error', async () => {
314
- await initializeProvider();
315
- fetchSpy.mockRejectedValueOnce(new Error('Network error'));
316
- const result = await provider.fetchPages(['site-001']);
317
- expect(result.isErr()).toBe(true);
318
- if (result.isErr()) {
319
- expect(result.error.message).toContain('Network error');
320
- }
321
- });
322
- });
323
- // --- fetchDocuments ---
324
- describe('fetchDocuments', () => {
325
- async function initializeProvider() {
326
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
327
- await provider.initialize(VALID_CONFIG);
328
- }
329
- it('should fetch documents from a site library', async () => {
330
- await initializeProvider();
331
- // Drives response
332
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
333
- // Drive items response
334
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDriveItem()] }));
335
- // File download (docx content)
336
- const emptyZip = new ArrayBuffer(0);
337
- fetchSpy.mockResolvedValueOnce(createMockResponse(emptyZip));
338
- const result = await provider.fetchDocuments('site-001');
339
- expect(result.isOk()).toBe(true);
340
- if (result.isOk()) {
341
- expect(result.value).toHaveLength(1);
342
- const doc = result.value[0];
343
- expect(doc.id).toBe('item-001');
344
- expect(doc.name).toBe('Architecture.docx');
345
- expect(doc.type).toBe('document');
346
- expect(doc.siteId).toBe('site-001');
347
- expect(doc.libraryName).toBe('Documents');
348
- expect(doc.mimeType).toContain('wordprocessingml');
349
- }
350
- });
351
- it('should filter by library name', async () => {
352
- await initializeProvider();
353
- // Drives: multiple libraries
354
- fetchSpy.mockResolvedValueOnce(createMockResponse({
355
- value: [
356
- createDrive({ id: 'drive-001', name: 'Documents' }),
357
- createDrive({ id: 'drive-002', name: 'Archives' }),
358
- ],
359
- }));
360
- // Only "Documents" drive should be queried
361
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
362
- const result = await provider.fetchDocuments('site-001', 'Documents');
363
- expect(result.isOk()).toBe(true);
364
- // Should only fetch items from the matching drive
365
- // The second fetchSpy call is for drive items of "Documents" only
366
- expect(fetchSpy).toHaveBeenCalledTimes(3); // token + drives + drive items
367
- });
368
- it('should skip unsupported file types', async () => {
369
- await initializeProvider();
370
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
371
- fetchSpy.mockResolvedValueOnce(createMockResponse({
372
- value: [
373
- createDriveItem({
374
- id: 'img-001',
375
- name: 'screenshot.png',
376
- file: { mimeType: 'image/png' },
377
- }),
378
- createDriveItem({
379
- id: 'doc-001',
380
- name: 'report.docx',
381
- file: {
382
- mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
383
- },
384
- }),
385
- ],
386
- }));
387
- // Only docx will trigger download
388
- fetchSpy.mockResolvedValueOnce(createMockResponse(new ArrayBuffer(0)));
389
- const result = await provider.fetchDocuments('site-001');
390
- expect(result.isOk()).toBe(true);
391
- if (result.isOk()) {
392
- expect(result.value).toHaveLength(1);
393
- expect(result.value[0].name).toBe('report.docx');
394
- }
395
- });
396
- it('should skip items without file property (folders)', async () => {
397
- await initializeProvider();
398
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
399
- fetchSpy.mockResolvedValueOnce(createMockResponse({
400
- value: [
401
- {
402
- id: 'folder-001',
403
- name: 'Reports',
404
- size: 0,
405
- webUrl: 'https://contoso.sharepoint.com/sites/eng/Documents/Reports',
406
- lastModifiedDateTime: '2026-02-10T10:00:00Z',
407
- // No file property — this is a folder
408
- },
409
- ],
410
- }));
411
- const result = await provider.fetchDocuments('site-001');
412
- expect(result.isOk()).toBe(true);
413
- if (result.isOk()) {
414
- expect(result.value).toEqual([]);
415
- }
416
- });
417
- it('should handle PDF documents', async () => {
418
- await initializeProvider();
419
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
420
- fetchSpy.mockResolvedValueOnce(createMockResponse({
421
- value: [
422
- createDriveItem({
423
- id: 'pdf-001',
424
- name: 'whitepaper.pdf',
425
- file: { mimeType: 'application/pdf' },
426
- }),
427
- ],
428
- }));
429
- // PDF download
430
- fetchSpy.mockResolvedValueOnce(createMockResponse(new ArrayBuffer(0)));
431
- const result = await provider.fetchDocuments('site-001');
432
- expect(result.isOk()).toBe(true);
433
- if (result.isOk()) {
434
- expect(result.value).toHaveLength(1);
435
- expect(result.value[0].mimeType).toBe('application/pdf');
436
- }
437
- });
438
- it('should return err on Graph API failure for drives', async () => {
439
- await initializeProvider();
440
- fetchSpy.mockResolvedValueOnce(createMockResponse({}, 404, 'Not Found'));
441
- const result = await provider.fetchDocuments('nonexistent-site');
442
- expect(result.isErr()).toBe(true);
443
- if (result.isErr()) {
444
- expect(result.error.message).toContain('404');
445
- }
446
- });
447
- it('should handle file download failure gracefully', async () => {
448
- await initializeProvider();
449
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
450
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDriveItem()] }));
451
- // Download fails
452
- fetchSpy.mockResolvedValueOnce(createMockResponse({}, 500, 'Internal Server Error'));
453
- const result = await provider.fetchDocuments('site-001');
454
- expect(result.isOk()).toBe(true);
455
- if (result.isOk()) {
456
- expect(result.value).toHaveLength(1);
457
- expect(result.value[0].plainText).toBe('');
458
- }
459
- });
460
- it('should return err on network failure', async () => {
461
- await initializeProvider();
462
- fetchSpy.mockRejectedValueOnce(new Error('Timeout'));
463
- const result = await provider.fetchDocuments('site-001');
464
- expect(result.isErr()).toBe(true);
465
- if (result.isErr()) {
466
- expect(result.error.message).toContain('Timeout');
467
- }
468
- });
469
- it('should handle pagination for drive items', async () => {
470
- await initializeProvider();
471
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
472
- // First page
473
- fetchSpy.mockResolvedValueOnce(createMockResponse({
474
- value: [createDriveItem({ id: 'doc-1', name: 'doc1.docx' })],
475
- '@odata.nextLink': 'https://graph.microsoft.com/v1.0/sites/site-001/drives/drive-001/root/children?$skiptoken=xyz',
476
- }));
477
- // Download doc-1
478
- fetchSpy.mockResolvedValueOnce(createMockResponse(new ArrayBuffer(0)));
479
- // Second page
480
- fetchSpy.mockResolvedValueOnce(createMockResponse({
481
- value: [createDriveItem({ id: 'doc-2', name: 'doc2.docx' })],
482
- }));
483
- // Download doc-2
484
- fetchSpy.mockResolvedValueOnce(createMockResponse(new ArrayBuffer(0)));
485
- const result = await provider.fetchDocuments('site-001');
486
- expect(result.isOk()).toBe(true);
487
- if (result.isOk()) {
488
- expect(result.value).toHaveLength(2);
489
- expect(result.value[0].id).toBe('doc-1');
490
- expect(result.value[1].id).toBe('doc-2');
491
- }
492
- });
493
- it('should use configured libraryNames when no explicit filter is given', async () => {
494
- await initializeProvider();
495
- // Returns two drives, only "Documents" matches config
496
- fetchSpy.mockResolvedValueOnce(createMockResponse({
497
- value: [
498
- createDrive({ id: 'drive-001', name: 'Documents' }),
499
- createDrive({ id: 'drive-002', name: 'Archives' }),
500
- ],
501
- }));
502
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
503
- const result = await provider.fetchDocuments('site-001');
504
- expect(result.isOk()).toBe(true);
505
- // Only "Documents" drive should be queried
506
- expect(fetchSpy).toHaveBeenCalledTimes(3); // token + drives + items
507
- });
508
- });
509
- // --- getChangedItems ---
510
- describe('getChangedItems', () => {
511
- async function initializeProvider() {
512
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
513
- await provider.initialize(VALID_CONFIG);
514
- }
515
- it('should fetch changed items since a date', async () => {
516
- await initializeProvider();
517
- // Drives for site-001
518
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
519
- // Delta response
520
- fetchSpy.mockResolvedValueOnce(createMockResponse({
521
- value: [
522
- {
523
- id: 'item-new',
524
- name: 'new-doc.docx',
525
- file: { mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' },
526
- lastModifiedDateTime: '2026-02-20T12:00:00Z',
527
- },
528
- {
529
- id: 'item-old',
530
- name: 'old-doc.docx',
531
- file: { mimeType: 'application/pdf' },
532
- lastModifiedDateTime: '2026-01-01T00:00:00Z',
533
- },
534
- ],
535
- '@odata.deltaLink': 'https://graph.microsoft.com/v1.0/drives/drive-001/root/delta?token=xyz',
536
- }));
537
- // Drives for site-002
538
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive({ id: 'drive-002', name: 'Documents' })] }));
539
- // Delta response for site-002
540
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
541
- const since = new Date('2026-02-15T00:00:00Z');
542
- const result = await provider.getChangedItems(since);
543
- expect(result.isOk()).toBe(true);
544
- if (result.isOk()) {
545
- // Only the item modified after "since" should be included
546
- expect(result.value).toHaveLength(1);
547
- expect(result.value[0].id).toBe('item-new');
548
- expect(result.value[0].type).toBe('document');
549
- expect(result.value[0].changeType).toBe('updated');
550
- }
551
- });
552
- it('should detect deleted items', async () => {
553
- await initializeProvider();
554
- // Drives for site-001
555
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
556
- fetchSpy.mockResolvedValueOnce(createMockResponse({
557
- value: [
558
- {
559
- id: 'item-deleted',
560
- name: 'removed.docx',
561
- deleted: { state: 'deleted' },
562
- lastModifiedDateTime: '2026-02-20T08:00:00Z',
563
- },
564
- ],
565
- }));
566
- // Drives for site-002
567
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive({ id: 'drive-002', name: 'Documents' })] }));
568
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
569
- const result = await provider.getChangedItems(new Date('2026-02-01'));
570
- expect(result.isOk()).toBe(true);
571
- if (result.isOk()) {
572
- expect(result.value).toHaveLength(1);
573
- expect(result.value[0].changeType).toBe('deleted');
574
- }
575
- });
576
- it('should return err when no site IDs configured', async () => {
577
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse()));
578
- await provider.initialize({
579
- tenantId: 'abc',
580
- clientId: 'def',
581
- clientSecret: 'ghi',
582
- });
583
- const result = await provider.getChangedItems(new Date());
584
- expect(result.isErr()).toBe(true);
585
- if (result.isErr()) {
586
- expect(result.error.message).toContain('No site IDs');
587
- }
588
- });
589
- it('should return err on Graph API failure', async () => {
590
- await initializeProvider();
591
- fetchSpy.mockResolvedValueOnce(createMockResponse({}, 500, 'Internal Server Error'));
592
- const result = await provider.getChangedItems(new Date());
593
- expect(result.isErr()).toBe(true);
594
- if (result.isErr()) {
595
- expect(result.error.message).toContain('500');
596
- }
597
- });
598
- it('should return empty array when no items changed', async () => {
599
- await initializeProvider();
600
- // Drives for site-001
601
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
602
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
603
- // Drives for site-002
604
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive({ id: 'drive-002', name: 'Documents' })] }));
605
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
606
- const result = await provider.getChangedItems(new Date());
607
- expect(result.isOk()).toBe(true);
608
- if (result.isOk()) {
609
- expect(result.value).toEqual([]);
610
- }
611
- });
612
- it('should handle delta pagination', async () => {
613
- await initializeProvider();
614
- // Drives for site-001
615
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive()] }));
616
- // First delta page
617
- fetchSpy.mockResolvedValueOnce(createMockResponse({
618
- value: [
619
- {
620
- id: 'change-1',
621
- name: 'doc1.docx',
622
- file: { mimeType: 'application/pdf' },
623
- lastModifiedDateTime: '2026-02-20T10:00:00Z',
624
- },
625
- ],
626
- '@odata.nextLink': 'https://graph.microsoft.com/v1.0/drives/drive-001/root/delta?$skiptoken=abc',
627
- }));
628
- // Second delta page
629
- fetchSpy.mockResolvedValueOnce(createMockResponse({
630
- value: [
631
- {
632
- id: 'change-2',
633
- name: 'doc2.pdf',
634
- file: { mimeType: 'application/pdf' },
635
- lastModifiedDateTime: '2026-02-21T10:00:00Z',
636
- },
637
- ],
638
- }));
639
- // Drives for site-002
640
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [createDrive({ id: 'drive-002', name: 'Documents' })] }));
641
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
642
- const result = await provider.getChangedItems(new Date('2026-02-01'));
643
- expect(result.isOk()).toBe(true);
644
- if (result.isOk()) {
645
- expect(result.value).toHaveLength(2);
646
- }
647
- });
648
- it('should return err on network error', async () => {
649
- await initializeProvider();
650
- fetchSpy.mockRejectedValueOnce(new Error('Connection refused'));
651
- const result = await provider.getChangedItems(new Date());
652
- expect(result.isErr()).toBe(true);
653
- if (result.isErr()) {
654
- expect(result.error.message).toContain('Connection refused');
655
- }
656
- });
657
- });
658
- // --- Error when not initialized ---
659
- describe('when not initialized', () => {
660
- it('should throw SharePointError from fetchPages', async () => {
661
- await expect(provider.fetchPages()).rejects.toThrow(SharePointError);
662
- });
663
- it('should throw SharePointError from fetchDocuments', async () => {
664
- await expect(provider.fetchDocuments('site-001')).rejects.toThrow(SharePointError);
665
- });
666
- it('should throw SharePointError from getChangedItems', async () => {
667
- await expect(provider.getChangedItems(new Date())).rejects.toThrow(SharePointError);
668
- });
669
- });
670
- // --- Token refresh ---
671
- describe('token refresh', () => {
672
- it('should re-acquire token when expired', async () => {
673
- // Initial token with very short expiry (already expired)
674
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse(30)));
675
- await provider.initialize(VALID_CONFIG);
676
- // Token refresh
677
- fetchSpy.mockResolvedValueOnce(createMockResponse(createTokenResponse(3600)));
678
- // Pages response
679
- fetchSpy.mockResolvedValueOnce(createMockResponse({ value: [] }));
680
- const result = await provider.fetchPages(['site-001']);
681
- expect(result.isOk()).toBe(true);
682
- // Should have made 3 calls: initial token + refresh token + pages
683
- expect(fetchSpy).toHaveBeenCalledTimes(3);
684
- });
685
- });
686
- });
687
- // ---------------------------------------------------------------------------
688
- // Text extraction
689
- // ---------------------------------------------------------------------------
690
- describe('extractTextFromDocx', () => {
691
- it('should return empty string for empty buffer', () => {
692
- expect(extractTextFromDocx(new ArrayBuffer(0))).toBe('');
693
- });
694
- it('should return empty string for non-ZIP data', () => {
695
- const buffer = new TextEncoder().encode('not a zip file').buffer;
696
- expect(extractTextFromDocx(buffer)).toBe('');
697
- });
698
- it('should extract text from a minimal docx-like ZIP structure', () => {
699
- // Build a minimal ZIP containing word/document.xml with <w:t> elements
700
- const xml = '<?xml version="1.0"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>Hello World</w:t></w:r></w:p><w:p><w:r><w:t>Second paragraph</w:t></w:r></w:p></w:body></w:document>';
701
- const buffer = buildMinimalZip('word/document.xml', xml);
702
- const result = extractTextFromDocx(buffer);
703
- expect(result).toContain('Hello World');
704
- expect(result).toContain('Second paragraph');
705
- });
706
- it('should handle multiple w:t elements in a single paragraph', () => {
707
- const xml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>Part 1 </w:t></w:r><w:r><w:t>Part 2</w:t></w:r></w:p></w:body></w:document>';
708
- const buffer = buildMinimalZip('word/document.xml', xml);
709
- const result = extractTextFromDocx(buffer);
710
- expect(result).toContain('Part 1');
711
- expect(result).toContain('Part 2');
712
- });
713
- it('should handle w:t with xml:space preserve attribute', () => {
714
- const xml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t xml:space="preserve"> spaced text </w:t></w:r></w:p></w:body></w:document>';
715
- const buffer = buildMinimalZip('word/document.xml', xml);
716
- const result = extractTextFromDocx(buffer);
717
- expect(result).toContain('spaced text');
718
- });
719
- });
720
- describe('extractTextFromPdf', () => {
721
- it('should return empty string for empty buffer', () => {
722
- expect(extractTextFromPdf(new ArrayBuffer(0))).toBe('');
723
- });
724
- it('should extract text from Tj operators', () => {
725
- const pdfContent = '%PDF-1.4\nBT\n/F1 12 Tf\n(Hello PDF World) Tj\nET\n';
726
- const buffer = new TextEncoder().encode(pdfContent).buffer;
727
- const result = extractTextFromPdf(buffer);
728
- expect(result).toContain('Hello PDF World');
729
- });
730
- it('should extract text from TJ array operators', () => {
731
- const pdfContent = '%PDF-1.4\nBT\n/F1 12 Tf\n[(First) -10 (Second) -20 (Third)] TJ\nET\n';
732
- const buffer = new TextEncoder().encode(pdfContent).buffer;
733
- const result = extractTextFromPdf(buffer);
734
- expect(result).toContain('First');
735
- expect(result).toContain('Second');
736
- expect(result).toContain('Third');
737
- });
738
- it('should handle escaped characters in PDF strings', () => {
739
- const pdfContent = '%PDF-1.4\nBT\n(Hello \\(world\\)) Tj\nET\n';
740
- const buffer = new TextEncoder().encode(pdfContent).buffer;
741
- const result = extractTextFromPdf(buffer);
742
- expect(result).toContain('Hello (world)');
743
- });
744
- it('should handle newline escapes in PDF strings', () => {
745
- const pdfContent = '%PDF-1.4\nBT\n(Line1\\nLine2) Tj\nET\n';
746
- const buffer = new TextEncoder().encode(pdfContent).buffer;
747
- const result = extractTextFromPdf(buffer);
748
- expect(result).toContain('Line1');
749
- expect(result).toContain('Line2');
750
- });
751
- it('should handle multiple BT/ET blocks', () => {
752
- const pdfContent = '%PDF-1.4\nBT\n(Block 1) Tj\nET\nBT\n(Block 2) Tj\nET\n';
753
- const buffer = new TextEncoder().encode(pdfContent).buffer;
754
- const result = extractTextFromPdf(buffer);
755
- expect(result).toContain('Block 1');
756
- expect(result).toContain('Block 2');
757
- });
758
- it('should return empty string for PDF without text content', () => {
759
- const pdfContent = '%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n';
760
- const buffer = new TextEncoder().encode(pdfContent).buffer;
761
- const result = extractTextFromPdf(buffer);
762
- expect(result).toBe('');
763
- });
764
- });
765
- // ---------------------------------------------------------------------------
766
- // ZIP builder helper (for docx tests)
767
- // ---------------------------------------------------------------------------
768
- /**
769
- * Builds a minimal ZIP file containing a single uncompressed entry.
770
- * This is a simplified ZIP structure for testing extractTextFromDocx.
771
- */
772
- function buildMinimalZip(filename, content) {
773
- const encoder = new TextEncoder();
774
- const filenameBytes = encoder.encode(filename);
775
- const contentBytes = encoder.encode(content);
776
- const filenameLen = filenameBytes.length;
777
- const contentLen = contentBytes.length;
778
- // Local file header (30 + filenameLen bytes)
779
- const localHeaderSize = 30 + filenameLen;
780
- // Central directory header (46 + filenameLen bytes)
781
- const centralHeaderSize = 46 + filenameLen;
782
- // End of central directory (22 bytes)
783
- const eocdSize = 22;
784
- const totalSize = localHeaderSize + contentLen + centralHeaderSize + eocdSize;
785
- const buffer = new ArrayBuffer(totalSize);
786
- const view = new DataView(buffer);
787
- const bytes = new Uint8Array(buffer);
788
- let offset = 0;
789
- // --- Local File Header ---
790
- view.setUint32(offset, 0x04034b50, true); // signature
791
- offset += 4;
792
- view.setUint16(offset, 20, true); // version needed
793
- offset += 2;
794
- view.setUint16(offset, 0, true); // flags
795
- offset += 2;
796
- view.setUint16(offset, 0, true); // compression method (store)
797
- offset += 2;
798
- view.setUint16(offset, 0, true); // mod time
799
- offset += 2;
800
- view.setUint16(offset, 0, true); // mod date
801
- offset += 2;
802
- view.setUint32(offset, 0, true); // CRC32 (simplified)
803
- offset += 4;
804
- view.setUint32(offset, contentLen, true); // compressed size
805
- offset += 4;
806
- view.setUint32(offset, contentLen, true); // uncompressed size
807
- offset += 4;
808
- view.setUint16(offset, filenameLen, true); // filename length
809
- offset += 2;
810
- view.setUint16(offset, 0, true); // extra field length
811
- offset += 2;
812
- bytes.set(filenameBytes, offset);
813
- offset += filenameLen;
814
- bytes.set(contentBytes, offset);
815
- offset += contentLen;
816
- // --- Central Directory Header ---
817
- const centralStart = offset;
818
- view.setUint32(offset, 0x02014b50, true); // signature
819
- offset += 4;
820
- view.setUint16(offset, 20, true); // version made by
821
- offset += 2;
822
- view.setUint16(offset, 20, true); // version needed
823
- offset += 2;
824
- view.setUint16(offset, 0, true); // flags
825
- offset += 2;
826
- view.setUint16(offset, 0, true); // compression method
827
- offset += 2;
828
- view.setUint16(offset, 0, true); // mod time
829
- offset += 2;
830
- view.setUint16(offset, 0, true); // mod date
831
- offset += 2;
832
- view.setUint32(offset, 0, true); // CRC32
833
- offset += 4;
834
- view.setUint32(offset, contentLen, true); // compressed size
835
- offset += 4;
836
- view.setUint32(offset, contentLen, true); // uncompressed size
837
- offset += 4;
838
- view.setUint16(offset, filenameLen, true); // filename length
839
- offset += 2;
840
- view.setUint16(offset, 0, true); // extra field length
841
- offset += 2;
842
- view.setUint16(offset, 0, true); // file comment length
843
- offset += 2;
844
- view.setUint16(offset, 0, true); // disk number start
845
- offset += 2;
846
- view.setUint16(offset, 0, true); // internal attrs
847
- offset += 2;
848
- view.setUint32(offset, 0, true); // external attrs
849
- offset += 4;
850
- view.setUint32(offset, 0, true); // local header offset
851
- offset += 4;
852
- bytes.set(filenameBytes, offset);
853
- offset += filenameLen;
854
- // --- End of Central Directory ---
855
- view.setUint32(offset, 0x06054b50, true); // signature
856
- offset += 4;
857
- view.setUint16(offset, 0, true); // disk number
858
- offset += 2;
859
- view.setUint16(offset, 0, true); // central dir disk
860
- offset += 2;
861
- view.setUint16(offset, 1, true); // entries on disk
862
- offset += 2;
863
- view.setUint16(offset, 1, true); // total entries
864
- offset += 2;
865
- view.setUint32(offset, centralHeaderSize, true); // central dir size
866
- offset += 4;
867
- view.setUint32(offset, centralStart, true); // central dir offset
868
- offset += 4;
869
- view.setUint16(offset, 0, true); // comment length
870
- offset += 2;
871
- return buffer;
872
- }
873
- //# sourceMappingURL=sharepoint-provider.test.js.map