mirror of
https://github.com/strapi/strapi.git
synced 2025-07-31 21:09:39 +00:00
Add tests for the provider & the entities
This commit is contained in:
parent
0937aef112
commit
bb75a2433d
@ -0,0 +1,152 @@
|
|||||||
|
import type { IEntity } from '../../../../types';
|
||||||
|
|
||||||
|
import { Readable, PassThrough } from 'stream';
|
||||||
|
|
||||||
|
import {
|
||||||
|
collect,
|
||||||
|
getStrapiFactory,
|
||||||
|
createMockedReadableFactory,
|
||||||
|
getContentTypes,
|
||||||
|
} from './test-utils';
|
||||||
|
import { createEntitiesStream, createEntitiesTransformStream } from '../entities';
|
||||||
|
|
||||||
|
describe('Local Strapi Source Provider - Entities Streaming', () => {
|
||||||
|
describe('Create Entities Stream', () => {
|
||||||
|
test('Should return an empty stream if there is no content type', async () => {
|
||||||
|
const customContentTypes = {};
|
||||||
|
const stream = createMockedReadableFactory<never>({});
|
||||||
|
|
||||||
|
const strapi = getStrapiFactory({
|
||||||
|
contentTypes: customContentTypes,
|
||||||
|
entityService: { stream },
|
||||||
|
})();
|
||||||
|
|
||||||
|
const entitiesStream = createEntitiesStream(strapi);
|
||||||
|
|
||||||
|
// The returned value should be a Readable stream instance
|
||||||
|
expect(entitiesStream).toBeInstanceOf(Readable);
|
||||||
|
|
||||||
|
const entities = await collect<IEntity<never>>(entitiesStream);
|
||||||
|
|
||||||
|
// The stream should not have been called since there is no content types
|
||||||
|
// Note: This check must happen AFTER we've collected the results
|
||||||
|
expect(stream).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// We have 0 * 0 entities
|
||||||
|
expect(entities).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should return a stream with 4 entities from 2 content types', async () => {
|
||||||
|
const stream = createMockedReadableFactory({
|
||||||
|
foo: [
|
||||||
|
{ id: 1, title: 'First title' },
|
||||||
|
{ id: 2, title: 'Second title' },
|
||||||
|
],
|
||||||
|
bar: [
|
||||||
|
{ id: 1, age: 42 },
|
||||||
|
{ id: 2, age: 84 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const strapi = getStrapiFactory({
|
||||||
|
contentTypes: getContentTypes(),
|
||||||
|
entityService: { stream },
|
||||||
|
})();
|
||||||
|
|
||||||
|
const entitiesStream = createEntitiesStream(strapi);
|
||||||
|
|
||||||
|
// The returned value should be a Readable stream instance
|
||||||
|
expect(entitiesStream).toBeInstanceOf(Readable);
|
||||||
|
|
||||||
|
const results = await collect(entitiesStream);
|
||||||
|
|
||||||
|
// Should have been called with 'foo', then 'bar'
|
||||||
|
// Note: This check must happen AFTER we've collected the results
|
||||||
|
expect(stream).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
// We have 2 * 2 entities
|
||||||
|
expect(results).toHaveLength(4);
|
||||||
|
|
||||||
|
const matchContentTypeUIDs = new RegExp(`(${Object.keys(getContentTypes()).join('|')})`);
|
||||||
|
|
||||||
|
// Each result should contain the entity and its parent content type
|
||||||
|
results.forEach((result) => {
|
||||||
|
expect(result).toMatchObject(
|
||||||
|
expect.objectContaining({
|
||||||
|
entity: expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
}),
|
||||||
|
contentType: expect.objectContaining({
|
||||||
|
uid: expect.stringMatching(matchContentTypeUIDs),
|
||||||
|
attributes: expect.any(Object),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Create Entities Transform Stream', () => {
|
||||||
|
test('Should transform entities to the Strapi Data Transfer Format', async () => {
|
||||||
|
const entities = [
|
||||||
|
{
|
||||||
|
entity: { id: 1, title: 'My first foo' },
|
||||||
|
contentType: { uid: 'foo' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 4, title: 'Another Foo' },
|
||||||
|
contentType: { uid: 'foo' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 12, title: 'Last foo' },
|
||||||
|
contentType: { uid: 'foo' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 1, age: 21 },
|
||||||
|
contentType: { uid: 'bar' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 2, age: 42 },
|
||||||
|
contentType: { uid: 'bar' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 7, age: 84 },
|
||||||
|
contentType: { uid: 'bar' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entity: { id: 9, age: 0 },
|
||||||
|
contentType: { uid: 'bar' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const matchContentTypeUIDs = new RegExp(
|
||||||
|
`(${Object.values(entities)
|
||||||
|
.map((entity) => entity.contentType.uid)
|
||||||
|
.join('|')})`
|
||||||
|
);
|
||||||
|
|
||||||
|
const entitiesStream = Readable.from(entities);
|
||||||
|
const transformStream = createEntitiesTransformStream();
|
||||||
|
|
||||||
|
expect(transformStream).toBeInstanceOf(PassThrough);
|
||||||
|
|
||||||
|
// Connect the data source to the transformation stream
|
||||||
|
const pipeline = entitiesStream.pipe(transformStream);
|
||||||
|
|
||||||
|
const transformedEntities = await collect(pipeline);
|
||||||
|
|
||||||
|
// Check the amount of transformed entities matches the initial amount
|
||||||
|
expect(transformedEntities).toHaveLength(entities.length);
|
||||||
|
|
||||||
|
// Each result should contain a type (uid), and id and some data
|
||||||
|
transformedEntities.forEach((entity) => {
|
||||||
|
expect(entity).toMatchObject(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: expect.stringMatching(matchContentTypeUIDs),
|
||||||
|
id: expect.any(Number),
|
||||||
|
data: expect.any(Object),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,112 @@
|
|||||||
|
import type { IEntity } from '../../../../types';
|
||||||
|
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
|
import { collect, getStrapiFactory } from './test-utils';
|
||||||
|
import { createLocalStrapiSourceProvider } from '../';
|
||||||
|
|
||||||
|
describe('Local Strapi Source Provider', () => {
|
||||||
|
describe('Boostrap', () => {
|
||||||
|
test('Should not have a defined Strapi instance if bootstrap has not been called', () => {
|
||||||
|
const provider = createLocalStrapiSourceProvider({ getStrapi: getStrapiFactory() });
|
||||||
|
|
||||||
|
expect(provider.strapi).not.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should have a defined Strapi instance if bootstrap has been called', async () => {
|
||||||
|
const provider = createLocalStrapiSourceProvider({ getStrapi: getStrapiFactory() });
|
||||||
|
await provider.bootstrap();
|
||||||
|
|
||||||
|
expect(provider.strapi).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Close', () => {
|
||||||
|
test('Should destroy the strapi instance if autoDestroy is undefined ', async () => {
|
||||||
|
const destroy = jest.fn();
|
||||||
|
|
||||||
|
const provider = createLocalStrapiSourceProvider({
|
||||||
|
getStrapi: getStrapiFactory({ destroy }),
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.bootstrap();
|
||||||
|
await provider.close();
|
||||||
|
|
||||||
|
expect(destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should destroy the strapi instance if autoDestroy is true ', async () => {
|
||||||
|
const destroy = jest.fn();
|
||||||
|
|
||||||
|
const provider = createLocalStrapiSourceProvider({
|
||||||
|
getStrapi: getStrapiFactory({ destroy }),
|
||||||
|
autoDestroy: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.bootstrap();
|
||||||
|
await provider.close();
|
||||||
|
|
||||||
|
expect(destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Streaming Entities', () => {
|
||||||
|
test('Should throw an error if strapi is not defined', async () => {
|
||||||
|
const provider = createLocalStrapiSourceProvider({ getStrapi: getStrapiFactory() });
|
||||||
|
|
||||||
|
await expect(() => provider.streamEntities()).rejects.toThrowError(
|
||||||
|
'Not able to stream entities. Strapi instance not found'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should successfully create a readable stream with all available entities', async () => {
|
||||||
|
const contentTypes = {
|
||||||
|
foo: { uid: 'foo', attributes: { title: { type: 'string' } } },
|
||||||
|
bar: { uid: 'bar', attributes: { age: { type: 'number' } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const stream = jest.fn((uid: string, _query: unknown) => {
|
||||||
|
if (uid === 'foo') {
|
||||||
|
return Readable.from([
|
||||||
|
{ id: 1, title: 'First title' },
|
||||||
|
{ id: 2, title: 'Second title' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid === 'bar') {
|
||||||
|
return Readable.from([
|
||||||
|
{ id: 1, age: 42 },
|
||||||
|
{ id: 2, age: 84 },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const provider = createLocalStrapiSourceProvider({
|
||||||
|
getStrapi: getStrapiFactory({
|
||||||
|
contentTypes,
|
||||||
|
entityService: { stream },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.bootstrap();
|
||||||
|
|
||||||
|
const entitiesStream = (await provider.streamEntities()) as Readable;
|
||||||
|
const entities = await collect<IEntity<'foo' | 'bar'>>(entitiesStream);
|
||||||
|
|
||||||
|
// Should have been called with 'foo', then 'bar'
|
||||||
|
expect(stream).toHaveBeenCalledTimes(2);
|
||||||
|
// The returned value should be a Readable stream instance
|
||||||
|
expect(entitiesStream).toBeInstanceOf(Readable);
|
||||||
|
// We have 2 * 2 entities
|
||||||
|
expect(entities).toHaveLength(4);
|
||||||
|
// Each entity should follow the transfer format
|
||||||
|
entities.forEach((entity) => {
|
||||||
|
expect(entity).toMatchObject({
|
||||||
|
type: expect.any(String),
|
||||||
|
id: expect.any(Number),
|
||||||
|
data: expect.any(Object),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,57 @@
|
|||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect every entity in a Readable stream
|
||||||
|
*/
|
||||||
|
export const collect = <T = unknown>(stream: Readable): Promise<T[]> => {
|
||||||
|
const chunks: T[] = [];
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
stream.on('data', (chunk) => chunks.push(chunk));
|
||||||
|
stream.on('end', () => {
|
||||||
|
stream.destroy();
|
||||||
|
resolve(chunks);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a "Strapi" like object factory based on the
|
||||||
|
* given params and cast it to the correct type
|
||||||
|
*/
|
||||||
|
export const getStrapiFactory =
|
||||||
|
<
|
||||||
|
T extends {
|
||||||
|
[key in keyof Partial<Strapi.Strapi>]: unknown;
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
properties?: T
|
||||||
|
) =>
|
||||||
|
() => {
|
||||||
|
return { ...properties } as Strapi.Strapi;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type used to represent the default content types available
|
||||||
|
*/
|
||||||
|
export type ContentType = 'foo' | 'bar';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to get default content types test values
|
||||||
|
*/
|
||||||
|
export const getContentTypes = (): {
|
||||||
|
[key in ContentType]: { uid: key; attributes: { [attribute: string]: unknown } };
|
||||||
|
} => ({
|
||||||
|
foo: { uid: 'foo', attributes: { title: { type: 'string' } } },
|
||||||
|
bar: { uid: 'bar', attributes: { age: { type: 'number' } } },
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a factory of readable streams (wrapped with a jest mock function)
|
||||||
|
*/
|
||||||
|
export const createMockedReadableFactory = <T extends string = ContentType>(source: {
|
||||||
|
[ct in T]: Array<{ id: number; [key: string]: unknown }>;
|
||||||
|
}) =>
|
||||||
|
jest.fn((uid: T) => {
|
||||||
|
return Readable.from(source[uid] || []);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user