Files
directus/api/src/services/specifications.test.ts
Rijk van Zanten 2983e61870 The Great TypeScript Modernization Program Season 3 Episode 6: The Big One (#18014)
* Step 1

* Step 2

* False sense of confidence

* Couple more before dinner

* Update schema package

* Update format-title

* Upgrade specs file

* Close

* Replace ts-node-dev with tsx, and various others

* Replace lodash with lodash-es

* Add lodash-es types

* Update knex import

* More fun is had

* FSE

* Consolidate repos

* Various tweaks and fixes

* Fix specs

* Remove dependency on knex-schema-inspector

* Fix wrong imports of inspector

* Move shared exceptions to new package

* Move constants to separate module

* Move types to new types package

* Use directus/types

* I believe this is no longer needed

* [WIP] Start moving utils to esm

* ESMify Shared

* Move shared utils to  @directus/utils

* Use @directus/utils instead of @directus/shared/utils

* It runs!

* Use correct schemaoverview type

* Fix imports

* Fix the thing

* Start on new update-checker lib

* Use new update-check package

* Swap out directus/shared in app

* Pushing through the last bits now

* Dangerously make extensions SDK ESM

* Use @directus/types in tests

* Copy util function to test

* Fix linter config

* Add missing import

* Hot takes

* Fix build

* Curse these default exports

* No tests in constants

* Add tests

* Remove tests from types

* Add tests for exceptions

* Fix test

* Fix app tests

* Fix import in test

* Fix various tests

* Fix specs export

* Some more tests

* Remove broken integration tests

These were broken beyond repair.. They were also written before we really knew what we we're doing with tests, so I think it's better to say goodbye and start over with these

* Regenerate lockfile

* Fix imports from merge

* I create my own problems

* Make sharp play nice

* Add vitest config

* Install missing blackbox dep

* Consts shouldn't be in types

tsk tsk tsk tsk

* Fix type/const usage in extensions-sdk

* cursed.default

* Reduce circular deps

* Fix circular dep in items service

* vvv

* Trigger testing for all vendors

* Add workaround for rollup

* Prepend the file protocol for the ESM loader to be compatible with Windows
"WARN: Only URLs with a scheme in: file and data are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'"

* Fix postgres

* Schema package updates

Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>

* Resolve cjs/mjs extensions

* Clean-up eslint config

* fixed extension concatination

* using string interpolation for consistency

* Revert MySQL optimisation

* Revert testing for all vendors

* Replace tsx with esbuild-kit/esm-loader

Is a bit faster and we can rely on the built-in `watch` and `inspect`
functionalities of Node.js

Note: The possibility to watch other files (.env in our case) might be
added in the future, see https://github.com/nodejs/node/issues/45467

* Use exact version for esbuild-kit/esm-loader

* Fix import

---------

Co-authored-by: ian <licitdev@gmail.com>
Co-authored-by: Brainslug <tim@brainslug.nl>
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
2023-04-04 17:41:56 -04:00

439 lines
12 KiB
TypeScript

import knex from 'knex';
import type { Knex } from 'knex';
import { createTracker, MockClient, Tracker } from 'knex-mock-client';
import { afterEach, beforeAll, beforeEach, describe, expect, it, MockedFunction, vi } from 'vitest';
import { CollectionsService, FieldsService, RelationsService, SpecificationService } from '../../src/services/index.js';
import type { Collection } from '../types/index.js';
class Client_PG extends MockClient {}
describe('Integration Tests', () => {
let db: MockedFunction<Knex>;
let tracker: Tracker;
beforeAll(async () => {
db = vi.mocked(knex.default({ client: Client_PG }));
tracker = createTracker(db);
});
afterEach(() => {
tracker.reset();
vi.clearAllMocks();
});
describe('Services / Specifications', () => {
describe('oas', () => {
describe('generate', () => {
let service: SpecificationService;
beforeEach(() => {
service = new SpecificationService({
knex: db,
schema: { collections: {}, relations: [] },
accountability: { role: 'admin', admin: true },
});
});
describe('schema', () => {
it('returns untyped schema for json fields', async () => {
vi.spyOn(CollectionsService.prototype, 'readByQuery').mockResolvedValue([
{
collection: 'test_table',
meta: {
accountability: 'all',
collection: 'test_table',
group: null,
hidden: false,
icon: null,
item_duplication_fields: null,
note: null,
singleton: false,
translations: null,
},
schema: {
name: 'test_table',
},
},
] as any[]);
vi.spyOn(FieldsService.prototype, 'readAll').mockResolvedValue([
{
collection: 'test_table',
field: 'id',
name: 'id',
type: 'integer',
meta: {
id: 1,
collection: 'test_table',
conditions: null,
display: null,
display_options: null,
field: 'id',
group: null,
hidden: true,
interface: null,
note: null,
options: null,
readonly: false,
required: false,
sort: null,
special: null,
translations: null,
validation: null,
validation_message: null,
width: 'full',
},
schema: {
comment: null,
data_type: 'integer',
default_value: null,
foreign_key_column: null,
foreign_key_schema: null,
foreign_key_table: null,
generation_expression: null,
has_auto_increment: false,
is_generated: false,
is_nullable: false,
is_primary_key: true,
is_unique: true,
max_length: null,
name: 'id',
numeric_precision: null,
numeric_scale: null,
table: 'test_table',
},
},
{
collection: 'test_table',
field: 'blob',
name: 'blob',
type: 'json',
meta: {
id: 2,
collection: 'test_table',
conditions: null,
display: null,
display_options: null,
field: 'blob',
group: null,
hidden: true,
interface: null,
note: null,
options: null,
readonly: false,
required: false,
sort: null,
special: null,
translations: null,
validation: null,
validation_message: null,
width: 'full',
},
schema: {
comment: null,
data_type: 'json',
default_value: null,
foreign_key_column: null,
foreign_key_schema: null,
foreign_key_table: null,
generation_expression: null,
has_auto_increment: false,
is_generated: false,
is_nullable: true,
is_primary_key: false,
is_unique: false,
max_length: null,
name: 'blob',
numeric_precision: null,
numeric_scale: null,
table: 'test_table',
},
},
]);
vi.spyOn(RelationsService.prototype, 'readAll').mockResolvedValue([]);
const spec = await service.oas.generate();
expect(spec.components?.schemas).toMatchInlineSnapshot(`
{
"Diff": {
"properties": {
"diff": {
"properties": {
"collections": {
"items": {
"properties": {
"collection": {
"type": "string",
},
"diff": {
"items": {
"type": "object",
},
"type": "array",
},
},
"type": "object",
},
"type": "array",
},
"fields": {
"items": {
"properties": {
"collection": {
"type": "string",
},
"diff": {
"items": {
"type": "object",
},
"type": "array",
},
"field": {
"type": "string",
},
},
"type": "object",
},
"type": "array",
},
"relations": {
"items": {
"properties": {
"collection": {
"type": "string",
},
"diff": {
"items": {
"type": "object",
},
"type": "array",
},
"field": {
"type": "string",
},
"related_collection": {
"type": "string",
},
},
"type": "object",
},
"type": "array",
},
},
"type": "object",
},
"hash": {
"type": "string",
},
},
"type": "object",
},
"ItemsTestTable": {
"properties": {
"blob": {
"nullable": true,
},
"id": {
"nullable": false,
"type": "integer",
},
},
"type": "object",
"x-collection": "test_table",
},
"Query": {
"properties": {
"deep": {
"description": "Deep allows you to set any of the other query parameters on a nested relational dataset.",
"example": {
"related_articles": {
"_limit": 3,
},
},
"type": "object",
},
"fields": {
"description": "Control what fields are being returned in the object.",
"example": [
"*",
"*.*",
],
"items": {
"type": "string",
},
"type": "array",
},
"filter": {
"example": {
"<field>": {
"<operator>": "<value>",
},
},
"type": "object",
},
"limit": {
"description": "Set the maximum number of items that will be returned",
"type": "number",
},
"offset": {
"description": "How many items to skip when fetching data.",
"type": "number",
},
"page": {
"description": "Cursor for use in pagination. Often used in combination with limit.",
"type": "number",
},
"search": {
"description": "Filter by items that contain the given search query in one of their fields.",
"type": "string",
},
"sort": {
"description": "How to sort the returned items.",
"example": [
"-date_created",
],
"items": {
"type": "string",
},
"type": "array",
},
},
"type": "object",
},
"Schema": {
"properties": {
"collections": {
"items": {
"$ref": "#/components/schemas/Collections",
},
"type": "array",
},
"directus": {
"type": "string",
},
"fields": {
"items": {
"$ref": "#/components/schemas/Fields",
},
"type": "array",
},
"relations": {
"items": {
"$ref": "#/components/schemas/Relations",
},
"type": "array",
},
"vendor": {
"type": "string",
},
"version": {
"example": 1,
"type": "integer",
},
},
"type": "object",
},
"x-metadata": {
"properties": {
"filter_count": {
"description": "Returns the item count of the collection you're querying, taking the current filter/search parameters into account.",
"type": "integer",
},
"total_count": {
"description": "Returns the total item count of the collection you're querying.",
"type": "integer",
},
},
"type": "object",
},
}
`);
});
});
describe('path', () => {
it('requestBody for CreateItems POST path should not have type in schema', async () => {
const collection: Collection = {
collection: 'test_table',
meta: {
accountability: 'all',
collection: 'test_table',
group: null,
hidden: false,
icon: null,
item_duplication_fields: null,
note: null,
singleton: false,
translations: {},
},
schema: {
name: 'test_table',
},
};
vi.spyOn(CollectionsService.prototype, 'readByQuery').mockResolvedValue([collection]);
vi.spyOn(FieldsService.prototype, 'readAll').mockResolvedValue([
{
collection: collection.collection,
field: 'id',
name: 'id',
type: 'integer',
meta: {
id: 1,
collection: 'test_table',
conditions: null,
display: null,
display_options: null,
field: 'id',
group: null,
hidden: true,
interface: null,
note: null,
options: null,
readonly: false,
required: false,
sort: null,
special: null,
translations: null,
validation: null,
validation_message: null,
width: 'full',
},
schema: {
comment: null,
data_type: 'integer',
default_value: null,
foreign_key_column: null,
foreign_key_schema: null,
foreign_key_table: null,
generation_expression: null,
has_auto_increment: false,
is_generated: false,
is_nullable: false,
is_primary_key: true,
is_unique: true,
max_length: null,
name: 'id',
numeric_precision: null,
numeric_scale: null,
table: 'test_table',
},
},
]);
vi.spyOn(RelationsService.prototype, 'readAll').mockResolvedValue([]);
const spec = await service.oas.generate();
const targetSchema =
spec.paths[`/items/${collection.collection}`].post.requestBody.content['application/json'].schema;
expect(targetSchema).toHaveProperty('oneOf');
expect(targetSchema).not.toHaveProperty('type');
});
});
});
});
});
});