Merge pull request #14859 from backstage/search/test-pipeline-improvements

This commit is contained in:
Eric Peterson
2022-11-25 16:06:26 +01:00
committed by GitHub
12 changed files with 134 additions and 45 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-backend-node': patch
---
Use of `TestPipeline.withSubject()` is now deprecated. Instead, use the `fromCollator`, `fromDecorator`, or `fromIndexer` static methods to instantiate a test pipeline. You may also use the class' `withCollator`, `withDecorator`, and `withIndexer` instance methods to build test pipelines that consist of multiple test subjects.
@@ -114,14 +114,14 @@ describe('DefaultCatalogCollatorFactory', () => {
});
it('fetches from the configured catalog service', async () => {
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog');
expect(documents).toHaveLength(expectedEntities.length);
});
it('maps a returned entity to an expected CatalogEntityDocument', async () => {
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(documents[0]).toMatchObject({
@@ -159,7 +159,7 @@ describe('DefaultCatalogCollatorFactory', () => {
});
collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(documents[0]).toMatchObject({
location: '/software/test-entity',
@@ -177,7 +177,7 @@ describe('DefaultCatalogCollatorFactory', () => {
});
collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
// The simulated 'Foo,Bar' filter should return in an empty list
@@ -191,7 +191,7 @@ describe('DefaultCatalogCollatorFactory', () => {
});
collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(documents).toHaveLength(expectedEntities.length);
@@ -137,7 +137,7 @@ describe('ElasticSearchSearchEngineIndexer', () => {
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
// Older indices should have been queried for.
expect(catSpy).toHaveBeenCalled();
@@ -184,7 +184,7 @@ describe('ElasticSearchSearchEngineIndexer', () => {
text: range(2000).join(', '),
}));
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
// Ensure multiple bulk requests were made.
expect(bulkSpy).toHaveBeenCalledTimes(2);
@@ -221,7 +221,7 @@ describe('ElasticSearchSearchEngineIndexer', () => {
catSpy,
);
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
// Final deletion shouldn't be called.
expect(deleteSpy).not.toHaveBeenCalled();
@@ -53,7 +53,7 @@ describe('PgSearchEngineIndexer', () => {
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
expect(database.getTransaction).toHaveBeenCalledTimes(1);
expect(database.prepareInsert).toHaveBeenCalledTimes(1);
@@ -73,7 +73,7 @@ describe('PgSearchEngineIndexer', () => {
location: `location-${i}`,
}));
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
expect(database.getTransaction).toHaveBeenCalledTimes(1);
expect(database.prepareInsert).toHaveBeenCalledTimes(1);
@@ -92,7 +92,7 @@ describe('PgSearchEngineIndexer', () => {
];
database.prepareInsert.mockRejectedValueOnce(expectedError);
const result = await TestPipeline.withSubject(indexer)
const result = await TestPipeline.fromIndexer(indexer)
.withDocuments(documents)
.execute();
@@ -114,7 +114,7 @@ describe('PgSearchEngineIndexer', () => {
];
database.insertDocuments.mockRejectedValueOnce(expectedError);
const result = await TestPipeline.withSubject(indexer)
const result = await TestPipeline.fromIndexer(indexer)
.withDocuments(documents)
.execute();
@@ -136,7 +136,7 @@ describe('PgSearchEngineIndexer', () => {
];
database.completeInsert.mockRejectedValueOnce(expectedError);
const result = await TestPipeline.withSubject(indexer)
const result = await TestPipeline.fromIndexer(indexer)
.withDocuments(documents)
.execute();
@@ -173,7 +173,14 @@ export type ScheduleTaskParameters = {
// @public
export class TestPipeline {
execute(): Promise<TestPipelineResult>;
static fromCollator(collator: Readable): TestPipeline;
static fromDecorator(decorator: Transform): TestPipeline;
static fromIndexer(indexer: Writable): TestPipeline;
withCollator(collator: Readable): this;
withDecorator(decorator: Transform): this;
withDocuments(documents: IndexableDocument[]): TestPipeline;
withIndexer(indexer: Writable): this;
// @deprecated
static withSubject(subject: Readable | Transform | Writable): TestPipeline;
}
@@ -142,7 +142,7 @@ describe('DefaultCatalogCollatorFactory', () => {
});
const collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(documents).toHaveLength(2);
@@ -381,7 +381,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -412,7 +412,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -452,7 +452,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -495,7 +495,7 @@ describe('LunrSearchEngine', () => {
inspectableSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -544,7 +544,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -583,7 +583,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -623,7 +623,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -663,7 +663,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -708,7 +708,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -763,10 +763,10 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index-2',
);
await TestPipeline.withSubject(indexer1)
await TestPipeline.fromIndexer(indexer1)
.withDocuments(mockDocuments)
.execute();
await TestPipeline.withSubject(indexer2)
await TestPipeline.fromIndexer(indexer2)
.withDocuments(mockDocuments2)
.execute();
@@ -811,7 +811,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -871,14 +871,14 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
const indexer2 = await getActualIndexer(
testLunrSearchEngine,
'test-index-2',
);
await TestPipeline.withSubject(indexer2)
await TestPipeline.fromIndexer(indexer2)
.withDocuments(mockDocuments2)
.execute();
@@ -924,7 +924,7 @@ describe('LunrSearchEngine', () => {
testLunrSearchEngine,
'test-index',
);
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -974,7 +974,7 @@ describe('LunrSearchEngine', () => {
}));
const indexer = await getActualIndexer(testLunrSearchEngine, 'test-index');
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -1112,7 +1112,7 @@ describe('stopword testing', () => {
const indexer = await getActualIndexer(testLunrSearchEngine, 'test-index');
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -1149,7 +1149,7 @@ describe('stopword testing', () => {
const indexer = await getActualIndexer(testLunrSearchEngine, 'test-index');
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -1186,7 +1186,7 @@ describe('stopword testing', () => {
const indexer = await getActualIndexer(testLunrSearchEngine, 'test-index');
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments(mockDocuments)
.execute();
@@ -58,7 +58,7 @@ describe('LunrSearchEngineIndexer', () => {
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
expect(lunrBuilderAddSpy).toHaveBeenCalledWith(documents[0]);
});
@@ -70,7 +70,7 @@ describe('LunrSearchEngineIndexer', () => {
location: `location-${i}`,
}));
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
expect(lunrBuilderAddSpy).toHaveBeenCalledTimes(350);
});
@@ -84,7 +84,7 @@ describe('LunrSearchEngineIndexer', () => {
},
];
await TestPipeline.withSubject(indexer).withDocuments(documents).execute();
await TestPipeline.fromIndexer(indexer).withDocuments(documents).execute();
// Builder ref should be set to location (and only once).
expect(lunrBuilderRefSpy).toHaveBeenCalledTimes(1);
@@ -46,7 +46,7 @@ describe('BatchSearchEngineIndexer', () => {
it('should work end-to-end', async () => {
const indexer = new ConcreteBatchIndexer({ batchSize: 1 });
await TestPipeline.withSubject(indexer)
await TestPipeline.fromIndexer(indexer)
.withDocuments([document, document, document])
.execute();
expect(indexSpy).toHaveBeenCalledTimes(3);
@@ -54,7 +54,7 @@ describe('DecoratorBase', () => {
}));
const decorator = new ConcreteDecorator();
const { documents } = await TestPipeline.withSubject(decorator)
const { documents } = await TestPipeline.fromDecorator(decorator)
.withDocuments([document, document, document])
.execute();
@@ -68,7 +68,7 @@ describe('DecoratorBase', () => {
decorateSpy.mockResolvedValue(undefined);
const decorator = new ConcreteDecorator();
const { documents } = await TestPipeline.withSubject(decorator)
const { documents } = await TestPipeline.fromDecorator(decorator)
.withDocuments([document, document, document])
.execute();
@@ -82,7 +82,7 @@ describe('DecoratorBase', () => {
});
const decorator = new ConcreteDecorator();
const { documents } = await TestPipeline.withSubject(decorator)
const { documents } = await TestPipeline.fromDecorator(decorator)
.withDocuments([document, document, document])
.execute();
@@ -37,6 +37,35 @@ export type TestPipelineResult = {
/**
* Test utility for Backstage Search collators, decorators, and indexers.
*
* @example
* An example test checking that a collator provides expected documents.
* ```
* it('provides expected documents', async () => {
* const testSubject = await yourCollatorFactory.getCollator();
* const pipeline = TestPipeline.fromCollator(testSubject);
*
* const { documents } = await pipeline.execute();
*
* expect(documents).toHaveLength(2);
* })
* ```
*
* @example
* An example test checking that a decorator behaves as expected.
* ```
* it('filters private documents', async () => {
* const testSubject = await yourDecoratorFactory.getDecorator();
* const pipeline = TestPipeline
* .fromDecorator(testSubject)
* .withDocuments([{ title: 'Private', location: '/private', text: '' }]);
*
* const { documents } = await pipeline.execute();
*
* expect(documents).toHaveLength(0);
* })
* ```
*
* @public
*/
export class TestPipeline {
@@ -60,6 +89,9 @@ export class TestPipeline {
/**
* Provide the collator, decorator, or indexer to be tested.
*
* @deprecated Use `fromCollator`, `fromDecorator` or `fromIndexer` static
* methods to create a test pipeline instead.
*/
static withSubject(subject: Readable | Transform | Writable) {
if (subject instanceof Transform) {
@@ -79,6 +111,51 @@ export class TestPipeline {
);
}
/**
* Create a test pipeline given a collator you want to test.
*/
static fromCollator(collator: Readable) {
return new TestPipeline({ collator });
}
/**
* Add a collator to the test pipeline.
*/
withCollator(collator: Readable): this {
this.collator = collator;
return this;
}
/**
* Create a test pipeline given a decorator you want to test.
*/
static fromDecorator(decorator: Transform) {
return new TestPipeline({ decorator });
}
/**
* Add a decorator to the test pipeline.
*/
withDecorator(decorator: Transform): this {
this.decorator = decorator;
return this;
}
/**
* Create a test pipeline given an indexer you want to test.
*/
static fromIndexer(indexer: Writable) {
return new TestPipeline({ indexer });
}
/**
* Add an indexer to the test pipeline.
*/
withIndexer(indexer: Writable): this {
this.indexer = indexer;
return this;
}
/**
* Provide documents for testing decorators and indexers.
*/
@@ -145,7 +145,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
it('fetches from the configured catalog and tech docs services', async () => {
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog');
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs');
@@ -153,7 +153,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
it('should create documents for each tech docs search index', async () => {
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
const entity = expectedEntities[0];
documents.forEach((document, idx) => {
@@ -182,7 +182,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
expect(documents[0]).toMatchObject({
@@ -200,7 +200,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
collator = await factory.getCollator();
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
// Only 1 entity with TechDocs configured multiplied by 3 pages.
@@ -222,7 +222,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
});
it('should create documents for each tech docs search index', async () => {
const pipeline = TestPipeline.withSubject(collator);
const pipeline = TestPipeline.fromCollator(collator);
const { documents } = await pipeline.execute();
const entity = expectedEntities[0];
documents.forEach((document, idx) => {