@directus/api 32.1.0 → 32.2.0
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.
- package/dist/ai/chat/constants/system-prompt.d.ts +1 -0
- package/dist/ai/chat/constants/system-prompt.js +51 -0
- package/dist/ai/chat/controllers/chat.post.d.ts +2 -0
- package/dist/ai/chat/controllers/chat.post.js +47 -0
- package/dist/ai/chat/lib/create-ui-stream.d.ts +15 -0
- package/dist/ai/chat/lib/create-ui-stream.js +42 -0
- package/dist/ai/chat/middleware/load-settings.d.ts +2 -0
- package/dist/ai/chat/middleware/load-settings.js +18 -0
- package/dist/ai/chat/models/chat-request.d.ts +34 -0
- package/dist/ai/chat/models/chat-request.js +26 -0
- package/dist/ai/chat/models/providers.d.ts +9 -0
- package/dist/ai/chat/models/providers.js +9 -0
- package/dist/ai/chat/router.d.ts +1 -0
- package/dist/ai/chat/router.js +5 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.d.ts +9 -0
- package/dist/ai/chat/utils/chat-request-tool-to-ai-sdk-tool.js +38 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.d.ts +12 -0
- package/dist/ai/chat/utils/fix-error-tool-calls.js +30 -0
- package/dist/ai/chat/utils/parse-json-schema-7.d.ts +13 -0
- package/dist/ai/chat/utils/parse-json-schema-7.js +75 -0
- package/dist/{mcp → ai/mcp}/server.d.ts +13 -16
- package/dist/{mcp → ai/mcp}/server.js +4 -13
- package/dist/ai/mcp/types.d.ts +15 -0
- package/dist/{mcp/tools/assets.js → ai/tools/assets/index.js} +8 -5
- package/dist/{mcp/tools/collections.js → ai/tools/collections/index.js} +7 -4
- package/dist/{mcp/tools/fields.js → ai/tools/fields/index.js} +12 -9
- package/dist/{mcp/tools/files.js → ai/tools/files/index.js} +11 -5
- package/dist/{mcp/tools/flows.js → ai/tools/flows/index.js} +11 -5
- package/dist/{mcp/tools/folders.js → ai/tools/folders/index.js} +12 -5
- package/dist/ai/tools/index.d.ts +15 -0
- package/dist/ai/tools/index.js +29 -0
- package/dist/{mcp/tools/items.js → ai/tools/items/index.js} +13 -6
- package/dist/{mcp/tools/prompts/items.md → ai/tools/items/prompt.md} +19 -15
- package/dist/{mcp/tools/operations.d.ts → ai/tools/operations/index.d.ts} +46 -0
- package/dist/{mcp/tools/operations.js → ai/tools/operations/index.js} +12 -5
- package/dist/{mcp/tools/relations.js → ai/tools/relations/index.js} +7 -4
- package/dist/{mcp/tools/schema.d.ts → ai/tools/schema/index.d.ts} +1 -1
- package/dist/{mcp/tools/schema.js → ai/tools/schema/index.js} +9 -6
- package/dist/{mcp/tools/system.js → ai/tools/system/index.js} +7 -4
- package/dist/{mcp/tools/trigger-flow.js → ai/tools/trigger-flow/index.js} +8 -5
- package/dist/{mcp → ai/tools}/types.d.ts +1 -17
- package/dist/ai/tools/utils.d.ts +9 -0
- package/dist/ai/tools/utils.js +17 -0
- package/dist/app.js +5 -0
- package/dist/auth/drivers/oauth2.d.ts +2 -1
- package/dist/auth/drivers/oauth2.js +17 -22
- package/dist/auth/drivers/openid.d.ts +2 -1
- package/dist/auth/drivers/openid.js +13 -18
- package/dist/auth/drivers/saml.js +6 -3
- package/dist/controllers/assets.js +39 -2
- package/dist/controllers/mcp.js +1 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +2 -2
- package/dist/database/migrations/20251103A-add-ai-settings.d.ts +3 -0
- package/dist/database/migrations/20251103A-add-ai-settings.js +14 -0
- package/dist/database/run-ast/run-ast.js +1 -1
- package/dist/extensions/lib/installation/manager.js +5 -9
- package/dist/extensions/lib/sync/status.d.ts +11 -0
- package/dist/extensions/lib/sync/status.js +34 -0
- package/dist/extensions/lib/sync/sync.d.ts +6 -0
- package/dist/extensions/lib/sync/sync.js +90 -0
- package/dist/extensions/lib/sync/tracker.d.ts +18 -0
- package/dist/extensions/lib/sync/tracker.js +71 -0
- package/dist/extensions/lib/sync/utils.d.ts +24 -0
- package/dist/extensions/lib/sync/utils.js +62 -0
- package/dist/extensions/manager.d.ts +8 -4
- package/dist/extensions/manager.js +30 -13
- package/dist/middleware/respond.js +2 -2
- package/dist/permissions/lib/fetch-policies.d.ts +1 -1
- package/dist/permissions/lib/fetch-roles-tree.d.ts +6 -3
- package/dist/permissions/lib/fetch-roles-tree.js +5 -27
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +9 -7
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +17 -9
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +1 -1
- package/dist/permissions/utils/fetch-raw-permissions.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.d.ts +1 -1
- package/dist/permissions/utils/fetch-share-info.js +1 -1
- package/dist/permissions/utils/filter-policies-by-ip.js +1 -1
- package/dist/permissions/utils/get-permissions-for-share.js +8 -8
- package/dist/permissions/utils/with-cache.d.ts +8 -6
- package/dist/permissions/utils/with-cache.js +12 -10
- package/dist/request/is-denied-ip.js +2 -2
- package/dist/services/assets/name-deduper.d.ts +7 -0
- package/dist/services/assets/name-deduper.js +23 -0
- package/dist/services/assets.d.ts +15 -2
- package/dist/services/assets.js +98 -5
- package/dist/services/authentication.js +4 -4
- package/dist/services/comments.js +2 -2
- package/dist/services/extensions.js +4 -0
- package/dist/services/folders.d.ts +27 -2
- package/dist/services/folders.js +75 -0
- package/dist/services/graphql/resolvers/query.js +1 -1
- package/dist/services/import-export.d.ts +1 -1
- package/dist/services/import-export.js +4 -5
- package/dist/services/notifications.js +2 -2
- package/dist/services/payload.js +20 -0
- package/dist/services/roles.js +2 -2
- package/dist/services/tus/server.js +3 -3
- package/dist/telemetry/utils/get-settings.d.ts +15 -0
- package/dist/telemetry/utils/get-settings.js +25 -9
- package/dist/test-utils/README.md +95 -24
- package/dist/test-utils/cache.d.ts +2 -2
- package/dist/test-utils/cache.js +2 -2
- package/dist/test-utils/{fields-service.d.ts → services/fields-service.d.ts} +1 -1
- package/dist/test-utils/{fields-service.js → services/fields-service.js} +3 -2
- package/dist/test-utils/services/files-service.d.ts +28 -0
- package/dist/test-utils/services/files-service.js +34 -0
- package/dist/test-utils/services/folders-service.d.ts +28 -0
- package/dist/test-utils/services/folders-service.js +33 -0
- package/dist/utils/encrypt.d.ts +2 -0
- package/dist/utils/encrypt.js +64 -0
- package/dist/utils/get-accountability-for-role.js +2 -2
- package/dist/utils/get-accountability-for-token.js +4 -4
- package/dist/utils/get-cache-key.js +2 -2
- package/dist/utils/is-login-redirect-allowed.d.ts +4 -0
- package/dist/{auth/utils → utils}/is-login-redirect-allowed.js +8 -16
- package/dist/utils/require-text.d.ts +1 -0
- package/dist/utils/require-text.js +4 -0
- package/dist/utils/require-yaml.js +2 -2
- package/package.json +31 -25
- package/dist/auth/utils/generate-callback-url.d.ts +0 -8
- package/dist/auth/utils/generate-callback-url.js +0 -11
- package/dist/auth/utils/is-login-redirect-allowed.d.ts +0 -8
- package/dist/extensions/lib/sync-extensions.d.ts +0 -3
- package/dist/extensions/lib/sync-extensions.js +0 -70
- package/dist/extensions/lib/sync-status.d.ts +0 -10
- package/dist/extensions/lib/sync-status.js +0 -27
- package/dist/mcp/tools/index.d.ts +0 -15
- package/dist/mcp/tools/index.js +0 -29
- package/dist/mcp/tools/prompts/index.d.ts +0 -16
- package/dist/mcp/tools/prompts/index.js +0 -19
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
- package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
- package/dist/utils/get-date-formatted.d.ts +0 -1
- package/dist/utils/get-date-formatted.js +0 -10
- package/dist/utils/ip-in-networks.d.ts +0 -6
- package/dist/utils/ip-in-networks.js +0 -13
- /package/dist/{mcp → ai/mcp}/index.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/index.js +0 -0
- /package/dist/{mcp → ai/mcp}/transport.d.ts +0 -0
- /package/dist/{mcp → ai/mcp}/transport.js +0 -0
- /package/dist/{mcp → ai/mcp}/types.js +0 -0
- /package/dist/{mcp/tools/assets.d.ts → ai/tools/assets/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/assets.md → ai/tools/assets/prompt.md} +0 -0
- /package/dist/{mcp/tools/collections.d.ts → ai/tools/collections/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/collections.md → ai/tools/collections/prompt.md} +0 -0
- /package/dist/{mcp/define.d.ts → ai/tools/define-tool.d.ts} +0 -0
- /package/dist/{mcp/define.js → ai/tools/define-tool.js} +0 -0
- /package/dist/{mcp/tools/fields.d.ts → ai/tools/fields/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/fields.md → ai/tools/fields/prompt.md} +0 -0
- /package/dist/{mcp/tools/files.d.ts → ai/tools/files/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/files.md → ai/tools/files/prompt.md} +0 -0
- /package/dist/{mcp/tools/flows.d.ts → ai/tools/flows/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/flows.md → ai/tools/flows/prompt.md} +0 -0
- /package/dist/{mcp/tools/folders.d.ts → ai/tools/folders/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/folders.md → ai/tools/folders/prompt.md} +0 -0
- /package/dist/{mcp/tools/items.d.ts → ai/tools/items/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/operations.md → ai/tools/operations/prompt.md} +0 -0
- /package/dist/{mcp/tools/relations.d.ts → ai/tools/relations/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/relations.md → ai/tools/relations/prompt.md} +0 -0
- /package/dist/{mcp/tools/prompts/schema.md → ai/tools/schema/prompt.md} +0 -0
- /package/dist/{mcp → ai/tools}/schema.d.ts +0 -0
- /package/dist/{mcp → ai/tools}/schema.js +0 -0
- /package/dist/{mcp/tools/system.d.ts → ai/tools/system/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt-description.md → ai/tools/system/prompt-description.md} +0 -0
- /package/dist/{mcp/tools/prompts/system-prompt.md → ai/tools/system/prompt.md} +0 -0
- /package/dist/{mcp/tools/trigger-flow.d.ts → ai/tools/trigger-flow/index.d.ts} +0 -0
- /package/dist/{mcp/tools/prompts/trigger-flow.md → ai/tools/trigger-flow/prompt.md} +0 -0
- /package/dist/{permissions/modules/fetch-global-access → ai/tools}/types.js +0 -0
- /package/dist/test-utils/{items-service.d.ts → services/items-service.d.ts} +0 -0
- /package/dist/test-utils/{items-service.js → services/items-service.js} +0 -0
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SettingsService } from '../../services/settings.js';
|
|
2
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
2
3
|
export const getSettings = async (db) => {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const settingsService = new SettingsService({
|
|
5
|
+
knex: db,
|
|
6
|
+
schema: await getSchema({ database: db }),
|
|
7
|
+
});
|
|
8
|
+
const settings = (await settingsService.readSingleton({
|
|
9
|
+
fields: [
|
|
10
|
+
'project_id',
|
|
11
|
+
'mcp_enabled',
|
|
12
|
+
'mcp_allow_deletes',
|
|
13
|
+
'mcp_system_prompt_enabled',
|
|
14
|
+
'visual_editor_urls',
|
|
15
|
+
'ai_openai_api_key',
|
|
16
|
+
'ai_anthropic_api_key',
|
|
17
|
+
'ai_system_prompt',
|
|
18
|
+
],
|
|
19
|
+
}));
|
|
7
20
|
return {
|
|
8
21
|
project_id: settings.project_id,
|
|
9
|
-
mcp_enabled:
|
|
10
|
-
mcp_allow_deletes:
|
|
11
|
-
mcp_system_prompt_enabled:
|
|
12
|
-
visual_editor_urls: settings.visual_editor_urls
|
|
22
|
+
mcp_enabled: settings?.mcp_enabled || false,
|
|
23
|
+
mcp_allow_deletes: settings?.mcp_allow_deletes || false,
|
|
24
|
+
mcp_system_prompt_enabled: settings?.mcp_system_prompt_enabled || false,
|
|
25
|
+
visual_editor_urls: settings.visual_editor_urls?.length || 0,
|
|
26
|
+
ai_openai_api_key: Boolean(settings?.ai_openai_api_key),
|
|
27
|
+
ai_anthropic_api_key: Boolean(settings?.ai_anthropic_api_key),
|
|
28
|
+
ai_system_prompt: Boolean(settings?.ai_system_prompt),
|
|
13
29
|
};
|
|
14
30
|
};
|
|
@@ -14,21 +14,23 @@ This directory contains mock implementations for commonly used modules in servic
|
|
|
14
14
|
- **[emitter.ts](#emitterts)** - Event emitter mocks
|
|
15
15
|
- **[items-service.ts](#items-servicets)** - ItemsService mocks
|
|
16
16
|
- **[fields-service.ts](#fields-servicets)** - FieldsService mocks
|
|
17
|
+
- **[files-service.ts](#files-servicets)** - FilesService mocks
|
|
18
|
+
- **[folders-service.ts](#folders-servicets)** - FoldersService mocks
|
|
17
19
|
- **[test-helpers.ts](#test-helpersts)** - Test data factory functions
|
|
18
20
|
|
|
19
21
|
## Quick Start
|
|
20
22
|
|
|
21
23
|
```typescript
|
|
22
|
-
import { createMockKnex, resetKnexMocks } from '../
|
|
24
|
+
import { createMockKnex, resetKnexMocks } from '../test-utils/knex.js';
|
|
23
25
|
|
|
24
26
|
// Set up mocks
|
|
25
27
|
vi.mock('../../src/database/index', async () => {
|
|
26
|
-
const { mockDatabase } = await import('../
|
|
28
|
+
const { mockDatabase } = await import('../test-utils/database.js');
|
|
27
29
|
return mockDatabase();
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
vi.mock('../cache.js', async () => {
|
|
31
|
-
const { mockCache } = await import('../
|
|
33
|
+
const { mockCache } = await import('../test-utils/cache.js');
|
|
32
34
|
return mockCache();
|
|
33
35
|
});
|
|
34
36
|
|
|
@@ -203,13 +205,13 @@ Creates a standard database module mock for service tests.
|
|
|
203
205
|
```typescript
|
|
204
206
|
// Standard PostgreSQL mock
|
|
205
207
|
vi.mock('../../src/database/index', async () => {
|
|
206
|
-
const { mockDatabase } = await import('../
|
|
208
|
+
const { mockDatabase } = await import('../test-utils/database.js');
|
|
207
209
|
return mockDatabase();
|
|
208
210
|
});
|
|
209
211
|
|
|
210
212
|
// MySQL-specific mock
|
|
211
213
|
vi.mock('../../src/database/index', async () => {
|
|
212
|
-
const { mockDatabase } = await import('../
|
|
214
|
+
const { mockDatabase } = await import('../test-utils/database.js');
|
|
213
215
|
return mockDatabase('mysql');
|
|
214
216
|
});
|
|
215
217
|
|
|
@@ -229,7 +231,7 @@ transaction wrapper).
|
|
|
229
231
|
|
|
230
232
|
```typescript
|
|
231
233
|
vi.mock('../utils/transaction.js', async () => {
|
|
232
|
-
const { mockTransaction } = await import('../
|
|
234
|
+
const { mockTransaction } = await import('../test-utils/database.js');
|
|
233
235
|
return mockTransaction();
|
|
234
236
|
});
|
|
235
237
|
|
|
@@ -269,13 +271,13 @@ mocks for vi.mock() declarations and spies for testing cache behavior.
|
|
|
269
271
|
```typescript
|
|
270
272
|
// Standard usage for vi.mock()
|
|
271
273
|
vi.mock('../cache.js', async () => {
|
|
272
|
-
const { mockCache } = await import('../
|
|
274
|
+
const { mockCache } = await import('../test-utils/cache.js');
|
|
273
275
|
return mockCache();
|
|
274
276
|
});
|
|
275
277
|
|
|
276
278
|
// Testing cache clearing with spies
|
|
277
279
|
import { getCache } from '../cache.js';
|
|
278
|
-
import { mockCache } from '../
|
|
280
|
+
import { mockCache } from '../test-utils/cache.js';
|
|
279
281
|
|
|
280
282
|
test('should clear cache after update', async () => {
|
|
281
283
|
const { spies } = mockCache();
|
|
@@ -305,7 +307,7 @@ Creates a standard schema inspector mock with tableInfo, columnInfo, primary, fo
|
|
|
305
307
|
```typescript
|
|
306
308
|
// Standard usage
|
|
307
309
|
vi.mock('@directus/schema', async () => {
|
|
308
|
-
const { mockSchema } = await import('../
|
|
310
|
+
const { mockSchema } = await import('../test-utils/schema.js');
|
|
309
311
|
return mockSchema();
|
|
310
312
|
});
|
|
311
313
|
|
|
@@ -340,7 +342,7 @@ Creates a standard emitter mock with emitAction, emitFilter, emitInit, and event
|
|
|
340
342
|
```typescript
|
|
341
343
|
// Standard usage
|
|
342
344
|
vi.mock('../emitter.js', async () => {
|
|
343
|
-
const { mockEmitter } = await import('../
|
|
345
|
+
const { mockEmitter } = await import('../test-utils/emitter.js');
|
|
344
346
|
return mockEmitter();
|
|
345
347
|
});
|
|
346
348
|
|
|
@@ -390,7 +392,7 @@ Creates a standard ItemsService mock with all CRUD methods pre-configured with s
|
|
|
390
392
|
```typescript
|
|
391
393
|
// Standard usage
|
|
392
394
|
vi.mock('./items.js', async () => {
|
|
393
|
-
const { mockItemsService } = await import('../
|
|
395
|
+
const { mockItemsService } = await import('../test-utils/services/items-service.js');
|
|
394
396
|
return mockItemsService();
|
|
395
397
|
});
|
|
396
398
|
|
|
@@ -423,6 +425,8 @@ Creates a standard FieldsService mock with common methods pre-configured.
|
|
|
423
425
|
|
|
424
426
|
**Mocked methods:**
|
|
425
427
|
|
|
428
|
+
In addition to the base `ItemsService` method the following `FieldsService` specific methods are available:
|
|
429
|
+
|
|
426
430
|
- `addColumnToTable` → no-op function
|
|
427
431
|
- `addColumnIndex` → resolves to undefined
|
|
428
432
|
- `deleteField` → resolves to undefined
|
|
@@ -434,7 +438,7 @@ Creates a standard FieldsService mock with common methods pre-configured.
|
|
|
434
438
|
```typescript
|
|
435
439
|
// Standard usage in CollectionsService tests
|
|
436
440
|
vi.mock('./fields.js', async () => {
|
|
437
|
-
const { mockFieldsService } = await import('../
|
|
441
|
+
const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
|
|
438
442
|
return mockFieldsService();
|
|
439
443
|
});
|
|
440
444
|
|
|
@@ -450,43 +454,110 @@ expect(addColumnIndexSpy).toHaveBeenCalled();
|
|
|
450
454
|
|
|
451
455
|
---
|
|
452
456
|
|
|
457
|
+
### files-service.ts
|
|
458
|
+
|
|
459
|
+
Provides FilesService mocking utilities for testing services that depend on FilesService.
|
|
460
|
+
|
|
461
|
+
#### `mockFilesService()`
|
|
462
|
+
|
|
463
|
+
Creates a standard FilesService mock with common methods pre-configured.
|
|
464
|
+
|
|
465
|
+
**Returns:** Mock module object with `FilesService` class
|
|
466
|
+
|
|
467
|
+
**Mocked methods:**
|
|
468
|
+
|
|
469
|
+
In addition to the base `ItemsService` method the following `FilesService` specific methods are available:
|
|
470
|
+
|
|
471
|
+
- `uploadOne` → `1`
|
|
472
|
+
- `importOne` → `1`
|
|
473
|
+
|
|
474
|
+
**Example:**
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// Standard usage in service tests
|
|
478
|
+
vi.mock('./files.js', async () => {
|
|
479
|
+
const { mockFilesService } = await import('../test-utils/services/files-service.js');
|
|
480
|
+
return mockFilesService();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// Override specific methods during tests
|
|
484
|
+
import { FilesService } from './files.js';
|
|
485
|
+
|
|
486
|
+
const uploadOneSpy = vi.spyOn(FilesService.prototype, 'uploadOne').mockResolvedValue(`1`);
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
### folders-service.ts
|
|
492
|
+
|
|
493
|
+
Provides FoldersService mocking utilities for testing services that depend on FoldersService.
|
|
494
|
+
|
|
495
|
+
#### `mockFilesService()`
|
|
496
|
+
|
|
497
|
+
Creates a standard FoldersService mock with common methods pre-configured.
|
|
498
|
+
|
|
499
|
+
**Returns:** Mock module object with `FoldersService` class
|
|
500
|
+
|
|
501
|
+
**Mocked methods:**
|
|
502
|
+
|
|
503
|
+
In addition to the base `ItemsService` method the following `FoldersService` specific methods are available:
|
|
504
|
+
|
|
505
|
+
- `buildTree` → return `1` => `root` map
|
|
506
|
+
|
|
507
|
+
**Example:**
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
// Standard usage in service tests
|
|
511
|
+
vi.mock('./folders.js', async () => {
|
|
512
|
+
const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
|
|
513
|
+
return mockFilesService();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Override specific methods during tests
|
|
517
|
+
import { FoldersService } from './folders.js';
|
|
518
|
+
|
|
519
|
+
const buildTreeSpy = vi.spyOn(FoldersService.prototype, 'buildTree').mockResolvedValue(new Map('1', 'root-alt'));
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
453
524
|
## Common Patterns
|
|
454
525
|
|
|
455
526
|
### Full Service Test Setup
|
|
456
527
|
|
|
457
528
|
```typescript
|
|
458
|
-
import { createMockKnex, resetKnexMocks } from '../
|
|
529
|
+
import { createMockKnex, resetKnexMocks } from '../test-utils/knex.js';
|
|
459
530
|
import { SchemaBuilder } from '@directus/schema-builder';
|
|
460
531
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
461
532
|
|
|
462
533
|
// Mock all dependencies (before imports)
|
|
463
534
|
vi.mock('../../src/database/index', async () => {
|
|
464
|
-
const { mockDatabase } = await import('../
|
|
535
|
+
const { mockDatabase } = await import('../test-utils/database.js');
|
|
465
536
|
return mockDatabase();
|
|
466
537
|
});
|
|
467
538
|
|
|
468
539
|
vi.mock('@directus/schema', async () => {
|
|
469
|
-
const { mockSchema } = await import('../
|
|
540
|
+
const { mockSchema } = await import('../test-utils/schema.js');
|
|
470
541
|
return mockSchema();
|
|
471
542
|
});
|
|
472
543
|
|
|
473
544
|
vi.mock('../cache.js', async () => {
|
|
474
|
-
const { mockCache } = await import('../
|
|
545
|
+
const { mockCache } = await import('../test-utils/cache.js');
|
|
475
546
|
return mockCache();
|
|
476
547
|
});
|
|
477
548
|
|
|
478
549
|
vi.mock('../emitter.js', async () => {
|
|
479
|
-
const { mockEmitter } = await import('../
|
|
550
|
+
const { mockEmitter } = await import('../test-utils/emitter.js');
|
|
480
551
|
return mockEmitter();
|
|
481
552
|
});
|
|
482
553
|
|
|
483
554
|
vi.mock('./items.js', async () => {
|
|
484
|
-
const { mockItemsService } = await import('../
|
|
555
|
+
const { mockItemsService } = await import('../test-utils/services/items-service.js');
|
|
485
556
|
return mockItemsService();
|
|
486
557
|
});
|
|
487
558
|
|
|
488
559
|
vi.mock('../utils/transaction.js', async () => {
|
|
489
|
-
const { mockTransaction } = await import('../
|
|
560
|
+
const { mockTransaction } = await import('../test-utils/database.js');
|
|
490
561
|
return mockTransaction();
|
|
491
562
|
});
|
|
492
563
|
|
|
@@ -525,7 +596,7 @@ describe('Integration Tests', () => {
|
|
|
525
596
|
### Testing Schema Operations
|
|
526
597
|
|
|
527
598
|
```typescript
|
|
528
|
-
import { mockCreateTable, mockAlterTable, createMockTableBuilder } from '../
|
|
599
|
+
import { mockCreateTable, mockAlterTable, createMockTableBuilder } from '../test-utils/knex.js';
|
|
529
600
|
|
|
530
601
|
test('should create table with correct schema', async () => {
|
|
531
602
|
const { db, mockSchemaBuilder } = createMockKnex();
|
|
@@ -552,7 +623,7 @@ test('should alter table to add column', async () => {
|
|
|
552
623
|
|
|
553
624
|
```typescript
|
|
554
625
|
import { getCache } from '../cache.js';
|
|
555
|
-
import { mockCache } from '../
|
|
626
|
+
import { mockCache } from '../test-utils/cache.js';
|
|
556
627
|
|
|
557
628
|
test('should clear cache after update', async () => {
|
|
558
629
|
const { spies } = mockCache();
|
|
@@ -604,7 +675,7 @@ test('should read column info', async () => {
|
|
|
604
675
|
### Testing with System Collection Mocks
|
|
605
676
|
|
|
606
677
|
```typescript
|
|
607
|
-
import { setupSystemCollectionMocks } from '../
|
|
678
|
+
import { setupSystemCollectionMocks } from '../test-utils/knex.js';
|
|
608
679
|
|
|
609
680
|
describe('Service Tests', () => {
|
|
610
681
|
const { db, tracker, mockSchemaBuilder } = createMockKnex();
|
|
@@ -678,7 +749,7 @@ Always declare `vi.mock()` calls **before** importing the modules they mock:
|
|
|
678
749
|
```typescript
|
|
679
750
|
// ✅ Correct - mocks first
|
|
680
751
|
vi.mock('../cache.js', async () => {
|
|
681
|
-
const { mockCache } = await import('../
|
|
752
|
+
const { mockCache } = await import('../test-utils/cache.js');
|
|
682
753
|
return mockCache();
|
|
683
754
|
});
|
|
684
755
|
|
|
@@ -688,7 +759,7 @@ import { YourService } from './your-service.js';
|
|
|
688
759
|
import { YourService } from './your-service.js';
|
|
689
760
|
|
|
690
761
|
vi.mock('../cache.js', async () => {
|
|
691
|
-
const { mockCache } = await import('../
|
|
762
|
+
const { mockCache } = await import('../test-utils/cache.js');
|
|
692
763
|
return mockCache();
|
|
693
764
|
});
|
|
694
765
|
```
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
* ```typescript
|
|
13
13
|
* // Standard usage for vi.mock()
|
|
14
14
|
* vi.mock('../cache.js', async () => {
|
|
15
|
-
* const { mockCache } = await import('../
|
|
15
|
+
* const { mockCache } = await import('../test-utils/cache.js');
|
|
16
16
|
* return mockCache();
|
|
17
17
|
* });
|
|
18
18
|
*
|
|
19
19
|
* // Testing cache clearing with spies
|
|
20
20
|
* import { getCache } from '../cache.js';
|
|
21
|
-
* import { mockCache } from '../
|
|
21
|
+
* import { mockCache } from '../test-utils/cache.js';
|
|
22
22
|
*
|
|
23
23
|
* test('should clear cache after update', async () => {
|
|
24
24
|
* const { spies } = mockCache();
|
package/dist/test-utils/cache.js
CHANGED
|
@@ -13,13 +13,13 @@ import { vi } from 'vitest';
|
|
|
13
13
|
* ```typescript
|
|
14
14
|
* // Standard usage for vi.mock()
|
|
15
15
|
* vi.mock('../cache.js', async () => {
|
|
16
|
-
* const { mockCache } = await import('../
|
|
16
|
+
* const { mockCache } = await import('../test-utils/cache.js');
|
|
17
17
|
* return mockCache();
|
|
18
18
|
* });
|
|
19
19
|
*
|
|
20
20
|
* // Testing cache clearing with spies
|
|
21
21
|
* import { getCache } from '../cache.js';
|
|
22
|
-
* import { mockCache } from '../
|
|
22
|
+
* import { mockCache } from '../test-utils/cache.js';
|
|
23
23
|
*
|
|
24
24
|
* test('should clear cache after update', async () => {
|
|
25
25
|
* const { spies } = mockCache();
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* ```typescript
|
|
13
13
|
* // Standard usage
|
|
14
14
|
* vi.mock('./fields.js', async () => {
|
|
15
|
-
* const { mockFieldsService } = await import('../
|
|
15
|
+
* const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
|
|
16
16
|
* return mockFieldsService();
|
|
17
17
|
* });
|
|
18
18
|
*
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Provides simplified mocks for src/services/fields module used in service testing
|
|
4
4
|
*/
|
|
5
5
|
import { vi } from 'vitest';
|
|
6
|
+
import { mockItemsService } from './items-service.js';
|
|
6
7
|
/**
|
|
7
8
|
* Creates a standard FieldsService mock for service tests
|
|
8
9
|
* This matches the pattern used in CollectionsService tests
|
|
@@ -13,7 +14,7 @@ import { vi } from 'vitest';
|
|
|
13
14
|
* ```typescript
|
|
14
15
|
* // Standard usage
|
|
15
16
|
* vi.mock('./fields.js', async () => {
|
|
16
|
-
* const { mockFieldsService } = await import('../
|
|
17
|
+
* const { mockFieldsService } = await import('../test-utils/services/fields-service.js');
|
|
17
18
|
* return mockFieldsService();
|
|
18
19
|
* });
|
|
19
20
|
*
|
|
@@ -25,7 +26,7 @@ import { vi } from 'vitest';
|
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
28
|
export function mockFieldsService() {
|
|
28
|
-
const FieldsService =
|
|
29
|
+
const { ItemsService: FieldsService } = mockItemsService();
|
|
29
30
|
// Mock common methods used by other services (like CollectionsService)
|
|
30
31
|
FieldsService.prototype.addColumnToTable = vi.fn().mockImplementation(() => { });
|
|
31
32
|
FieldsService.prototype.addColumnIndex = vi.fn().mockResolvedValue(undefined);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FilesService mocking utilities for service tests
|
|
3
|
+
* Provides simplified mocks for src/services/files module used in service testing
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Creates a standard FilesService mock for service tests
|
|
7
|
+
* This matches the pattern used in CollectionsService tests
|
|
8
|
+
*
|
|
9
|
+
* @returns Mock module object for vi.mock()
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Standard usage
|
|
14
|
+
* vi.mock('./files.js', async () => {
|
|
15
|
+
* const { mockFilesService } = await import('../test-utils/services/files-service.js');
|
|
16
|
+
* return mockFilesService();
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // To dynamically change FilesService behavior during tests:
|
|
20
|
+
* import { FilesService } from './files.js';
|
|
21
|
+
* vi.spyOn(FilesService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
|
|
22
|
+
* // custom implementation
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function mockFilesService(): {
|
|
27
|
+
FilesService: import("vitest").Mock<(...args: any[]) => any>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FilesService mocking utilities for service tests
|
|
3
|
+
* Provides simplified mocks for src/services/files module used in service testing
|
|
4
|
+
*/
|
|
5
|
+
import { vi } from 'vitest';
|
|
6
|
+
import { mockItemsService } from './items-service.js';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a standard FilesService mock for service tests
|
|
9
|
+
* This matches the pattern used in CollectionsService tests
|
|
10
|
+
*
|
|
11
|
+
* @returns Mock module object for vi.mock()
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Standard usage
|
|
16
|
+
* vi.mock('./files.js', async () => {
|
|
17
|
+
* const { mockFilesService } = await import('../test-utils/services/files-service.js');
|
|
18
|
+
* return mockFilesService();
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // To dynamically change FilesService behavior during tests:
|
|
22
|
+
* import { FilesService } from './files.js';
|
|
23
|
+
* vi.spyOn(FilesService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
|
|
24
|
+
* // custom implementation
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function mockFilesService() {
|
|
29
|
+
const { ItemsService: FilesService } = mockItemsService();
|
|
30
|
+
// non-crud methods
|
|
31
|
+
FilesService.prototype.uploadOne = vi.fn().mockResolvedValue(1);
|
|
32
|
+
FilesService.prototype.importOne = vi.fn().mockResolvedValue(1);
|
|
33
|
+
return { FilesService };
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FoldersService mocking utilities for service tests
|
|
3
|
+
* Provides simplified mocks for src/services/folders module used in service testing
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Creates a standard FoldersService mock for service tests
|
|
7
|
+
* This matches the pattern used in CollectionsService tests
|
|
8
|
+
*
|
|
9
|
+
* @returns Mock module object for vi.mock()
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Standard usage
|
|
14
|
+
* vi.mock('./folders.js', async () => {
|
|
15
|
+
* const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
|
|
16
|
+
* return mockFoldersService();
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // To dynamically change FoldersService behavior during tests:
|
|
20
|
+
* import { FoldersService } from './folders.js';
|
|
21
|
+
* vi.spyOn(FoldersService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
|
|
22
|
+
* // custom implementation
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function mockFoldersService(): {
|
|
27
|
+
FoldersService: import("vitest").Mock<(...args: any[]) => any>;
|
|
28
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FoldersService mocking utilities for service tests
|
|
3
|
+
* Provides simplified mocks for src/services/folders module used in service testing
|
|
4
|
+
*/
|
|
5
|
+
import { vi } from 'vitest';
|
|
6
|
+
import { mockItemsService } from './items-service.js';
|
|
7
|
+
/**
|
|
8
|
+
* Creates a standard FoldersService mock for service tests
|
|
9
|
+
* This matches the pattern used in CollectionsService tests
|
|
10
|
+
*
|
|
11
|
+
* @returns Mock module object for vi.mock()
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Standard usage
|
|
16
|
+
* vi.mock('./folders.js', async () => {
|
|
17
|
+
* const { mockFoldersService } = await import('../test-utils/services/folders-service.js');
|
|
18
|
+
* return mockFoldersService();
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // To dynamically change FoldersService behavior during tests:
|
|
22
|
+
* import { FoldersService } from './folders.js';
|
|
23
|
+
* vi.spyOn(FoldersService.prototype, 'addColumnToTable').mockImplementation((table, collection, field) => {
|
|
24
|
+
* // custom implementation
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function mockFoldersService() {
|
|
29
|
+
const { ItemsService: FoldersService } = mockItemsService();
|
|
30
|
+
// non-crud methods
|
|
31
|
+
FoldersService.prototype.buildTree = vi.fn().mockResolvedValue(new Map([['1', 'root']]));
|
|
32
|
+
return { FoldersService };
|
|
33
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const VERSION = '1';
|
|
4
|
+
const KDF = 'scrypt';
|
|
5
|
+
const SCRYPT_DEFAULTS = { N: 2 ** 14, r: 8, p: 1 }; // ~16MB mem, good server default
|
|
6
|
+
const scryptAsync = promisify(crypto.scrypt);
|
|
7
|
+
const deriveKey = async (password, salt, opts = SCRYPT_DEFAULTS) => {
|
|
8
|
+
return await scryptAsync(password, salt, 32, opts);
|
|
9
|
+
};
|
|
10
|
+
export const encrypt = async (plainText, password) => {
|
|
11
|
+
const salt = crypto.randomBytes(16);
|
|
12
|
+
const keyBuf = await deriveKey(password, salt, SCRYPT_DEFAULTS);
|
|
13
|
+
// Generate a 12-byte IV for GCM and keep base64 string for storage
|
|
14
|
+
const ivBuf = crypto.randomBytes(12);
|
|
15
|
+
const iv = ivBuf.toString('base64');
|
|
16
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', keyBuf, ivBuf);
|
|
17
|
+
let cipherText = cipher.update(plainText, 'utf8', 'base64');
|
|
18
|
+
cipherText += cipher.final('base64');
|
|
19
|
+
const tag = cipher.getAuthTag().toString('base64');
|
|
20
|
+
// 1||scrypt||N||r||p||salt||iv||cipherText||tag
|
|
21
|
+
return [
|
|
22
|
+
VERSION,
|
|
23
|
+
KDF,
|
|
24
|
+
SCRYPT_DEFAULTS.N,
|
|
25
|
+
SCRYPT_DEFAULTS.r,
|
|
26
|
+
SCRYPT_DEFAULTS.p,
|
|
27
|
+
salt.toString('base64'),
|
|
28
|
+
iv,
|
|
29
|
+
cipherText,
|
|
30
|
+
tag,
|
|
31
|
+
].join('||');
|
|
32
|
+
};
|
|
33
|
+
export const decrypt = async (encryptedText, password) => {
|
|
34
|
+
const parts = encryptedText.split('||');
|
|
35
|
+
if (parts.length < 9)
|
|
36
|
+
throw new Error('Invalid encrypted payload');
|
|
37
|
+
const [version, kdf, nStr, rStr, pStr, saltB64, ivB64, cipherText, tagB64] = parts;
|
|
38
|
+
if (version !== VERSION)
|
|
39
|
+
throw new Error(`Unsupported version: ${version}`);
|
|
40
|
+
if (kdf !== KDF)
|
|
41
|
+
throw new Error(`Unsupported kdf: ${kdf}`);
|
|
42
|
+
if (!saltB64)
|
|
43
|
+
throw new Error('No salt in encrypted string');
|
|
44
|
+
if (!ivB64)
|
|
45
|
+
throw new Error('No iv in encrypted string');
|
|
46
|
+
if (cipherText === undefined)
|
|
47
|
+
throw new Error('No cipherText in encrypted string');
|
|
48
|
+
if (!tagB64)
|
|
49
|
+
throw new Error('No tag in encrypted string');
|
|
50
|
+
const opts = {
|
|
51
|
+
N: Number(nStr) || SCRYPT_DEFAULTS.N,
|
|
52
|
+
r: Number(rStr) || SCRYPT_DEFAULTS.r,
|
|
53
|
+
p: Number(pStr) || SCRYPT_DEFAULTS.p,
|
|
54
|
+
};
|
|
55
|
+
const salt = Buffer.from(saltB64, 'base64');
|
|
56
|
+
const keyBuf = await deriveKey(password, salt, opts);
|
|
57
|
+
const iv = Buffer.from(ivB64, 'base64');
|
|
58
|
+
const tag = Buffer.from(tagB64, 'base64');
|
|
59
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', keyBuf, iv);
|
|
60
|
+
decipher.setAuthTag(tag);
|
|
61
|
+
let plaintext = decipher.update(cipherText, 'base64', 'utf8');
|
|
62
|
+
plaintext += decipher.final('utf8');
|
|
63
|
+
return plaintext;
|
|
64
|
+
};
|
|
@@ -13,13 +13,13 @@ export async function getAccountabilityForRole(role, context) {
|
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
else {
|
|
16
|
-
const roles = await fetchRolesTree(role, context.database);
|
|
16
|
+
const roles = await fetchRolesTree(role, { knex: context.database });
|
|
17
17
|
// The roles tree should always include the passed role. If it doesn't, it's because it
|
|
18
18
|
// couldn't be read from the database and therefore doesn't exist
|
|
19
19
|
if (roles.length === 0) {
|
|
20
20
|
throw new Error(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
|
|
21
21
|
}
|
|
22
|
-
const globalAccess = await fetchGlobalAccess({ user: null, roles, ip: context.accountability?.ip ?? null }, context.database);
|
|
22
|
+
const globalAccess = await fetchGlobalAccess({ user: null, roles, ip: context.accountability?.ip ?? null }, { knex: context.database });
|
|
23
23
|
generatedAccountability = createDefaultAccountability({
|
|
24
24
|
role,
|
|
25
25
|
roles,
|
|
@@ -25,8 +25,8 @@ export async function getAccountabilityForToken(token, accountability) {
|
|
|
25
25
|
if (payload.id)
|
|
26
26
|
accountability.user = payload.id;
|
|
27
27
|
accountability.role = payload.role;
|
|
28
|
-
accountability.roles = await fetchRolesTree(payload.role, database);
|
|
29
|
-
const { admin, app } = await fetchGlobalAccess(accountability, database);
|
|
28
|
+
accountability.roles = await fetchRolesTree(payload.role, { knex: database });
|
|
29
|
+
const { admin, app } = await fetchGlobalAccess(accountability, { knex: database });
|
|
30
30
|
accountability.admin = admin;
|
|
31
31
|
accountability.app = app;
|
|
32
32
|
}
|
|
@@ -44,8 +44,8 @@ export async function getAccountabilityForToken(token, accountability) {
|
|
|
44
44
|
}
|
|
45
45
|
accountability.user = user.id;
|
|
46
46
|
accountability.role = user.role;
|
|
47
|
-
accountability.roles = await fetchRolesTree(user.role, database);
|
|
48
|
-
const { admin, app } = await fetchGlobalAccess(accountability, database);
|
|
47
|
+
accountability.roles = await fetchRolesTree(user.role, { knex: database });
|
|
48
|
+
const { admin, app } = await fetchGlobalAccess(accountability, { knex: database });
|
|
49
49
|
accountability.admin = admin;
|
|
50
50
|
accountability.app = app;
|
|
51
51
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { ipInNetworks } from '@directus/utils/node';
|
|
2
|
+
import { version } from 'directus/version';
|
|
1
3
|
import hash from 'object-hash';
|
|
2
4
|
import url from 'url';
|
|
3
5
|
import getDatabase from '../database/index.js';
|
|
4
6
|
import { fetchPoliciesIpAccess } from '../permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js';
|
|
5
7
|
import { getGraphqlQueryAndVariables } from './get-graphql-query-and-variables.js';
|
|
6
|
-
import { version } from 'directus/version';
|
|
7
|
-
import { ipInNetworks } from './ip-in-networks.js';
|
|
8
8
|
export async function getCacheKey(req) {
|
|
9
9
|
const path = url.parse(req.originalUrl).pathname;
|
|
10
10
|
const isGraphQl = path?.startsWith('/graphql');
|