diff --git a/.changeset/calm-bottles-happen.md b/.changeset/calm-bottles-happen.md new file mode 100644 index 0000000000..e76db28065 --- /dev/null +++ b/.changeset/calm-bottles-happen.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': minor +--- + +**BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. +The `read` method is now deprecated and will be removed in a future release. diff --git a/.changeset/few-books-remember.md b/.changeset/few-books-remember.md new file mode 100644 index 0000000000..4607202ff6 --- /dev/null +++ b/.changeset/few-books-remember.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts index 13d85be4d4..6e932d3cea 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts @@ -27,8 +27,6 @@ import { UrlReaderPredicateTuple, } from './types'; -const MIN_WARNING_INTERVAL_MS = 1000 * 60 * 15; - function notAllowedMessage(url: string) { return ( `Reading from '${url}' is not allowed. ` + @@ -43,7 +41,6 @@ function notAllowedMessage(url: string) { */ export class UrlReaderPredicateMux implements UrlReader { private readonly readers: UrlReaderPredicateTuple[] = []; - private readonly readerWarnings: Map = new Map(); constructor(private readonly logger: Logger) {} @@ -71,23 +68,7 @@ export class UrlReaderPredicateMux implements UrlReader { for (const { predicate, reader } of this.readers) { if (predicate(parsed)) { - if (reader.readUrl) { - return reader.readUrl(url, options); - } - const now = Date.now(); - const lastWarned = this.readerWarnings.get(reader) ?? 0; - if (now > lastWarned + MIN_WARNING_INTERVAL_MS) { - this.readerWarnings.set(reader, now); - this.logger.warn( - `No implementation of readUrl found for ${reader}, this method will be required in the ` + - `future and will replace the 'read' method. See the changelog for more details here: ` + - 'https://github.com/backstage/backstage/blob/master/packages/backend-common/CHANGELOG.md#085', - ); - } - const buffer = await reader.read(url); - return { - buffer: async () => buffer, - }; + return reader.readUrl(url, options); } } diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index 6eae4e6cac..2ebf38c08a 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -27,6 +27,7 @@ import { AbortSignal } from 'node-abort-controller'; export type UrlReader = { /** * Reads a single file and return its content. + * @deprecated use readUrl instead. */ read(url: string): Promise; @@ -38,10 +39,9 @@ export type UrlReader = { * This is a replacement for the read method that supports options and * complex responses. * - * Use this whenever it is available, as the read method will be - * deprecated and eventually removed in a future release. + * Use this as the read method will be removed in a future release. */ - readUrl?(url: string, options?: ReadUrlOptions): Promise; + readUrl(url: string, options?: ReadUrlOptions): Promise; /** * Reads a full or partial file tree. diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts index 21080202b5..9f97e621c2 100644 --- a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts @@ -48,6 +48,7 @@ describe('OpenApiRefProcessor', () => { const config = new ConfigReader({}); const reader = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts index 71154c5473..9cb4d414c0 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts @@ -33,34 +33,24 @@ describe('CodeOwnersProcessor', () => { target: `https://github.com/backstage/backstage/blob/master/${basePath}catalog-info.yaml`, }); - const mockReadResult = ({ - error = undefined, - data = undefined, - }: { - error?: string; - data?: string; - } = {}) => { - if (error) { - throw Error(error); - } - return data; - }; - describe('preProcessEntity', () => { const setupTest = ({ kind = 'Component', spec = {} } = {}) => { const entity = { kind, spec }; - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() })); - const config = new ConfigReader({}); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(mockCodeOwnersText()), + }), + }; const processor = CodeOwnersProcessor.fromConfig(config, { logger: getVoidLogger(), reader, }); - return { entity, processor, read }; + return { entity, processor }; }; it('should not modify existing owner', async () => { diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts index 49189b20de..c1d2301536 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { findCodeOwnerByTarget, readCodeOwners } from './read'; @@ -25,35 +26,32 @@ const mockCodeowners = ` /docs @acme/team-bar `; -const mockReadResult = ({ - error = undefined, - data = undefined, -}: { - error?: string; - data?: string; -} = {}) => { - if (error) { - throw Error(error); - } - return data; -}; - describe('readCodeOwners', () => { it('should return found codeowners file', async () => { - const ownersText = mockCodeowners; - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from(mockCodeowners)), + }), + readTree: jest.fn(), + search: jest.fn(), + }; + const result = await readCodeOwners(reader, sourceUrl, [ '.github/CODEOWNERS', ]); - expect(result).toEqual(ownersText); + expect(result).toEqual(mockCodeowners); }); it('should return undefined when no codeowner', async () => { - const read = jest.fn().mockRejectedValue(mockReadResult()); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockRejectedValue(undefined), + }), + readTree: jest.fn(), + search: jest.fn(), + }; await expect( readCodeOwners(reader, sourceUrl, ['.github/CODEOWNERS']), @@ -61,22 +59,31 @@ describe('readCodeOwners', () => { }); it('should look at multiple locations', async () => { - const ownersText = mockCodeowners; - const read = jest - .fn() - .mockImplementationOnce(() => mockReadResult({ error: 'not found' })) - .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest + .fn() + .mockRejectedValue(new NotFoundError('not found')) + .mockResolvedValue(mockCodeowners), + }), + readTree: jest.fn(), + search: jest.fn(), + }; const result = await readCodeOwners(reader, sourceUrl, [ '.github/CODEOWNERS', 'docs/CODEOWNERS', ]); - expect(read.mock.calls.length).toBe(2); - expect(read.mock.calls[0]).toEqual([`${sourceUrl}.github/CODEOWNERS`]); - expect(read.mock.calls[1]).toEqual([`${sourceUrl}docs/CODEOWNERS`]); - expect(result).toEqual(ownersText); + expect(reader.readUrl.mock.calls.length).toBe(2); + expect(reader.readUrl.mock.calls[0]).toEqual([ + `${sourceUrl}.github/CODEOWNERS`, + ]); + expect(reader.readUrl.mock.calls[1]).toEqual([ + `${sourceUrl}docs/CODEOWNERS`, + ]); + expect(result).toEqual(mockCodeowners); }); }); @@ -85,15 +92,18 @@ describe('findCodeOwnerByLocation', () => { target = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', codeownersContents: codeOwnersContents = mockCodeowners, }: { target?: string; codeownersContents?: string } = {}) => { - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: codeOwnersContents })); - const scmIntegration = ScmIntegrations.fromConfig( new ConfigReader({}), ).byUrl(target); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(codeOwnersContents), + }), + readTree: jest.fn(), + search: jest.fn(), + }; return { target, reader, scmIntegration, codeOwnersContents }; }; diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts index c6b89efcc9..2987e162cc 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts @@ -28,14 +28,9 @@ export async function readCodeOwners( ): Promise { const readOwnerLocation = async (path: string): Promise => { const url = `${sourceUrl}${path}`; - - if (reader.readUrl) { - const data = await reader.readUrl(url); - const buffer = await data.buffer(); - return buffer.toString(); - } - const data = await reader.read(url); - return data.toString(); + const data = await reader.readUrl(url); + const buffer = await data.buffer(); + return buffer.toString(); }; const candidates = codeownersPaths.map(readOwnerLocation); diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 105ab03d90..903ac1ac6a 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -31,8 +31,12 @@ import { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); describe('PlaceholderProcessor', () => { - const read: jest.MockedFunction = jest.fn(); - const reader: UrlReader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader: jest.Mocked = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + readUrl: jest.fn(), + }; beforeEach(() => { jest.resetAllMocks(); @@ -86,7 +90,7 @@ describe('PlaceholderProcessor', () => { spec: { a: [{ b: 'TEXT' }] }, }); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); expect(upperResolver).toHaveBeenCalledWith( expect.objectContaining({ key: 'upper', @@ -115,7 +119,7 @@ describe('PlaceholderProcessor', () => { processor.preProcessEntity(entity, { type: 'a', target: 'b' }, () => {}), ).resolves.toEqual(entity); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('ignores unknown placeholders', async () => { @@ -136,11 +140,13 @@ describe('PlaceholderProcessor', () => { processor.preProcessEntity(entity, { type: 'a', target: 'b' }, () => {}), ).resolves.toEqual(entity); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('works with the text resolver', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -169,15 +175,19 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/file.txt', ); }); it('works with the json resolver', async () => { - read.mockResolvedValue( - Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), - ); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue( + Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), + ), + }); const processor = new PlaceholderProcessor({ resolvers: { json: jsonPlaceholderResolver }, reader, @@ -206,13 +216,17 @@ describe('PlaceholderProcessor', () => { spec: { data: { a: ['b', 7] } }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/b/file.json', ); }); it('works with the yaml resolver', async () => { - read.mockResolvedValue(Buffer.from('foo:\n - bar: 7', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue(Buffer.from('foo:\n - bar: 7', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { yaml: yamlPlaceholderResolver }, reader, @@ -241,13 +255,15 @@ describe('PlaceholderProcessor', () => { spec: { data: { foo: [{ bar: 7 }] } }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/file.yaml', ); }); it('resolves absolute path for absolute location', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -280,13 +296,15 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/catalog-info.yaml', ); }); it('resolves absolute path for relative file location', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -318,7 +336,7 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/catalog-info.yaml', ); }); @@ -327,7 +345,9 @@ describe('PlaceholderProcessor', () => { // We explicitly don't support this case, as it would allow for file system // traversal attacks. If we want to implement this, we need to have additional // security measures in place! - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -356,12 +376,16 @@ describe('PlaceholderProcessor', () => { /^Placeholder \$text could not form a URL out of \.\/a\/b\/catalog-info\.yaml and \.\.\/c\/catalog-info\.yaml, TypeError \[ERR_INVALID_URL\]/, ); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('should emit the resolverValue as a refreshKey', async () => { - read.mockResolvedValue( - Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), - ); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue( + Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), + ), + }); const processor = new PlaceholderProcessor({ resolvers: { diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts index 5e49c67915..e6b5ee279d 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts @@ -120,12 +120,9 @@ export class PlaceholderProcessor implements CatalogProcessor { } const read = async (url: string): Promise => { - if (this.options.reader.readUrl) { - const response = await this.options.reader.readUrl(url); - const buffer = await response.buffer(); - return buffer; - } - return this.options.reader.read(url); + const response = await this.options.reader.readUrl(url); + const buffer = await response.buffer(); + return buffer; }; const resolveUrl = (url: string, base: string): string => diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index cf14ea22e0..14c5710afc 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -195,6 +195,7 @@ describe('UrlReaderProcessor', () => { const reader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn().mockImplementation(async () => []), }; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 491f643637..fe9f21ebb4 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -135,16 +135,10 @@ export class UrlReaderProcessor implements CatalogProcessor { return { response: await Promise.all(output), etag: response.etag }; } - // Otherwise do a plain read, prioritizing readUrl if available - if (this.options.reader.readUrl) { - const data = await this.options.reader.readUrl(location, { etag }); - return { - response: [{ url: location, data: await data.buffer() }], - etag: data.etag, - }; - } - - const data = await this.options.reader.read(location); - return { response: [{ url: location, data }] }; + const data = await this.options.reader.readUrl(location, { etag }); + return { + response: [{ url: location, data: await data.buffer() }], + etag: data.etag, + }; } } diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index 23d343f354..bfa4aa0507 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -74,6 +74,7 @@ describe('fetch:cookiecutter', () => { }; const mockReader: UrlReader = { + readUrl: jest.fn(), read: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index 597847130a..98ac3a403f 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -75,6 +75,7 @@ describe('fetch:rails', () => { const mockReader: UrlReader = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts index 3c58c833ff..50ec94e099 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts @@ -40,6 +40,7 @@ describe('fetchContent helper', () => { const readTree = jest.fn(); const reader: UrlReader = { read: jest.fn(), + readUrl: jest.fn(), readTree, search: jest.fn(), }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index 740f27a623..1595578666 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -33,6 +33,7 @@ describe('fetch:plain', () => { }), ); const reader: UrlReader = { + readUrl: jest.fn(), read: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index f0ee42ba4a..5751a460bf 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -54,6 +54,7 @@ export async function startStandaloneServer( const discovery = SingleHostDiscovery.fromConfig(config); const mockUrlReader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index 69f09d92ed..1b25abded3 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -16,6 +16,8 @@ import { ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, SearchResponse, UrlReader, } from '@backstage/backend-common'; @@ -292,6 +294,13 @@ describe('getDocFilesFromRepository', () => { return Buffer.from('mock'); } + async readUrl( + _url: string, + _options?: ReadUrlOptions | undefined, + ): Promise { + throw new Error('Method not implemented.'); + } + async readTree(): Promise { return { dir: async () => { diff --git a/plugins/techdocs-node/src/stages/prepare/dir.test.ts b/plugins/techdocs-node/src/stages/prepare/dir.test.ts index 829c414ddb..ddefb40f03 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.test.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.test.ts @@ -46,6 +46,7 @@ const createMockEntity = (annotations: {}) => { const mockConfig = new ConfigReader({}); const mockUrlReader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts index 3e074f78a6..1f2b79a55f 100644 --- a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts +++ b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts @@ -30,6 +30,7 @@ function mockReader(): jest.Mocked { read: jest.fn(), readTree: jest.fn(), search: jest.fn(), + readUrl: jest.fn(), } as jest.Mocked; }