mirror of
https://github.com/strapi/strapi.git
synced 2025-07-29 20:10:21 +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