directus
1import type { Knex } from 'knex';
2import knex from 'knex';
3import { createTracker, MockClient, Tracker } from 'knex-mock-client';
4import type { MockedFunction } from 'vitest';
5import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
6import { SpecificationService } from './index.js';
7import type { CollectionsOverview } from '@directus/types';
8
9class Client_PG extends MockClient {}
10
11describe('Integration Tests', () => {
12let db: MockedFunction<Knex>;
13let tracker: Tracker;
14
15beforeAll(async () => {
16db = vi.mocked(knex.default({ client: Client_PG }));
17tracker = createTracker(db);
18});
19
20afterEach(() => {
21tracker.reset();
22vi.clearAllMocks();
23});
24
25describe('Services / Specifications', () => {
26describe('oas', () => {
27describe('generate', () => {
28describe('schema', () => {
29it('returns untyped schema for json fields', async () => {
30const service = new SpecificationService({
31knex: db,
32schema: {
33collections: {
34test_table: {
35collection: 'test_table',
36primary: 'id',
37singleton: false,
38sortField: null,
39accountability: 'all',
40note: null,
41fields: {
42id: {
43field: 'id',
44type: 'integer',
45nullable: false,
46generated: false,
47defaultValue: null,
48dbType: 'integer',
49precision: null,
50scale: null,
51special: [],
52note: null,
53validation: null,
54alias: false,
55},
56blob: {
57field: 'blob',
58type: 'json',
59dbType: 'json',
60defaultValue: null,
61nullable: true,
62generated: false,
63precision: null,
64scale: null,
65special: [],
66note: null,
67alias: false,
68validation: null,
69},
70},
71},
72} as CollectionsOverview,
73relations: [],
74},
75accountability: { role: 'admin', admin: true },
76});
77
78const spec = await service.oas.generate();
79
80expect(spec.components?.schemas).toMatchInlineSnapshot(`
81{
82"Diff": {
83"properties": {
84"diff": {
85"properties": {
86"collections": {
87"items": {
88"properties": {
89"collection": {
90"type": "string",
91},
92"diff": {
93"items": {
94"type": "object",
95},
96"type": "array",
97},
98},
99"type": "object",
100},
101"type": "array",
102},
103"fields": {
104"items": {
105"properties": {
106"collection": {
107"type": "string",
108},
109"diff": {
110"items": {
111"type": "object",
112},
113"type": "array",
114},
115"field": {
116"type": "string",
117},
118},
119"type": "object",
120},
121"type": "array",
122},
123"relations": {
124"items": {
125"properties": {
126"collection": {
127"type": "string",
128},
129"diff": {
130"items": {
131"type": "object",
132},
133"type": "array",
134},
135"field": {
136"type": "string",
137},
138"related_collection": {
139"type": "string",
140},
141},
142"type": "object",
143},
144"type": "array",
145},
146},
147"type": "object",
148},
149"hash": {
150"type": "string",
151},
152},
153"type": "object",
154},
155"ItemsTestTable": {
156"properties": {
157"blob": {
158"nullable": true,
159},
160"id": {
161"nullable": false,
162"type": "integer",
163},
164},
165"type": "object",
166"x-collection": "test_table",
167},
168"Query": {
169"properties": {
170"deep": {
171"description": "Deep allows you to set any of the other query parameters on a nested relational dataset.",
172"example": {
173"related_articles": {
174"_limit": 3,
175},
176},
177"type": "object",
178},
179"fields": {
180"description": "Control what fields are being returned in the object.",
181"example": [
182"*",
183"*.*",
184],
185"items": {
186"type": "string",
187},
188"type": "array",
189},
190"filter": {
191"example": {
192"<field>": {
193"<operator>": "<value>",
194},
195},
196"type": "object",
197},
198"limit": {
199"description": "Set the maximum number of items that will be returned",
200"type": "number",
201},
202"offset": {
203"description": "How many items to skip when fetching data.",
204"type": "number",
205},
206"page": {
207"description": "Cursor for use in pagination. Often used in combination with limit.",
208"type": "number",
209},
210"search": {
211"description": "Filter by items that contain the given search query in one of their fields.",
212"type": "string",
213},
214"sort": {
215"description": "How to sort the returned items.",
216"example": [
217"-date_created",
218],
219"items": {
220"type": "string",
221},
222"type": "array",
223},
224},
225"type": "object",
226},
227"Schema": {
228"properties": {
229"collections": {
230"items": {
231"$ref": "#/components/schemas/Collections",
232},
233"type": "array",
234},
235"directus": {
236"type": "string",
237},
238"fields": {
239"items": {
240"$ref": "#/components/schemas/Fields",
241},
242"type": "array",
243},
244"relations": {
245"items": {
246"$ref": "#/components/schemas/Relations",
247},
248"type": "array",
249},
250"vendor": {
251"type": "string",
252},
253"version": {
254"example": 1,
255"type": "integer",
256},
257},
258"type": "object",
259},
260"x-metadata": {
261"properties": {
262"filter_count": {
263"description": "Returns the item count of the collection you're querying, taking the current filter/search parameters into account.",
264"type": "integer",
265},
266"total_count": {
267"description": "Returns the total item count of the collection you're querying.",
268"type": "integer",
269},
270},
271"type": "object",
272},
273}
274`);
275});
276});
277
278describe('path', () => {
279it('requestBody for CreateItems POST path should not have type in schema', async () => {
280const service = new SpecificationService({
281knex: db,
282schema: {
283collections: {
284test_table: {
285collection: 'test_table',
286primary: 'id',
287singleton: false,
288sortField: null,
289accountability: 'all',
290note: null,
291fields: {
292id: {
293field: 'id',
294type: 'integer',
295nullable: false,
296generated: false,
297defaultValue: null,
298dbType: 'integer',
299precision: null,
300scale: null,
301special: [],
302note: null,
303validation: null,
304alias: false,
305},
306},
307},
308} as CollectionsOverview,
309relations: [],
310},
311accountability: { role: 'admin', admin: true },
312});
313
314const spec = await service.oas.generate();
315
316const targetSchema = spec.paths['/items/test_table']?.post?.requestBody?.content['application/json'].schema;
317
318expect(targetSchema).toHaveProperty('oneOf');
319expect(targetSchema).not.toHaveProperty('type');
320});
321});
322});
323});
324});
325});
326