UNPKG

@backstage/backend-test-utils

Version:

Test helpers library for Backstage backends

790 lines (776 loc) • 30.5 kB
import Keyv from 'keyv'; import { Knex } from 'knex'; import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api'; import { ServiceFactory, RootConfigService, RootLoggerService, AuthService, DiscoveryService, BackstageCredentials, HttpAuthService, BackstageUserInfo, UserInfoService, DatabaseService, PermissionsService, SchedulerService, RootInstanceMetadataService, BackstageNonePrincipal, BackstageUserPrincipal, BackstagePrincipalAccessRestrictions, BackstageServicePrincipal, ServiceRef, ExtensionPoint, BackendFeature } from '@backstage/backend-plugin-api'; import { EventsService } from '@backstage/plugin-events-node'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { JsonObject } from '@backstage/types'; import { Backend } from '@backstage/backend-app-api'; import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; import * as express from 'express'; import * as qs from 'qs'; import * as express_serve_static_core from 'express-serve-static-core'; /** * The possible caches to test against. * * @public */ type TestCacheId = 'MEMORY' | 'REDIS_7' | 'VALKEY_8' | 'MEMCACHED_1'; /** * Encapsulates the creation of ephemeral test cache instances for use inside * unit or integration tests. * * @public */ declare class TestCaches { private readonly instanceById; private readonly supportedIds; private static defaultIds?; /** * Creates an empty `TestCaches` instance, and sets up Jest to clean up all of * its acquired resources after all tests finish. * * You typically want to create just a single instance like this at the top of * your test file or `describe` block, and then call `init` many times on that * instance inside the individual tests. Spinning up a "physical" cache * instance takes a considerable amount of time, slowing down tests. But * wiping the contents of an instance using `init` is very fast. */ static create(options?: { ids?: TestCacheId[]; disableDocker?: boolean; }): TestCaches; static setDefaults(options: { ids?: TestCacheId[]; }): void; private constructor(); supports(id: TestCacheId): boolean; eachSupportedId(): [TestCacheId][]; /** * Returns a fresh, empty cache for the given driver. * * @param id - The ID of the cache to use, e.g. 'REDIS_7' * @returns Cache connection properties */ init(id: TestCacheId): Promise<{ store: string; connection: string; keyv: Keyv; }>; private initAny; private initMemcached; private initRedis; private initValkey; private shutdown; } /** * The possible databases to test against. * * @public */ type TestDatabaseId = 'POSTGRES_18' | 'POSTGRES_17' | 'POSTGRES_16' | 'POSTGRES_15' | 'POSTGRES_14' | 'POSTGRES_13' | 'POSTGRES_12' | 'POSTGRES_11' | 'POSTGRES_9' | 'MYSQL_8' | 'SQLITE_3'; /** * Encapsulates the creation of ephemeral test database instances for use * inside unit or integration tests. * * @public */ declare class TestDatabases { private readonly engineFactoryByDriver; private readonly engineByTestDatabaseId; private readonly supportedIds; private static defaultIds?; /** * Creates an empty `TestDatabases` instance, and sets up Jest to clean up * all of its acquired resources after all tests finish. * * You typically want to create just a single instance like this at the top * of your test file or `describe` block, and then call `init` many times on * that instance inside the individual tests. Spinning up a "physical" * database instance takes a considerable amount of time, slowing down tests. * But initializing a new logical database inside that instance using `init` * is very fast. */ static create(options?: { ids?: TestDatabaseId[]; disableDocker?: boolean; }): TestDatabases; static setDefaults(options: { ids?: TestDatabaseId[]; }): void; private constructor(); supports(id: TestDatabaseId): boolean; eachSupportedId(): [TestDatabaseId][]; /** * Returns a fresh, unique, empty logical database on an instance of the * given database ID platform. * * @param id - The ID of the database platform to use, e.g. 'POSTGRES_14' * @returns A `Knex` connection object */ init(id: TestDatabaseId): Promise<Knex>; private shutdown; } /** * Sets up handlers for request mocking * @public * @param worker - service worker */ declare function registerMswTestHooks(worker: { listen: (t: any) => void; close: () => void; resetHandlers: () => void; }): void; /** * A context that allows for more advanced file system operations when writing mock directory content. * * @public */ interface MockDirectoryContentCallbackContext { /** Absolute path to the location of this piece of content on the filesystem */ path: string; /** Creates a symbolic link at the current location */ symlink(target: string): void; } /** * A callback that allows for more advanced file system operations when writing mock directory content. * * @public */ type MockDirectoryContentCallback = (ctx: MockDirectoryContentCallbackContext) => void; /** * The content of a mock directory represented by a nested object structure. * * @remarks * * When used as input, the keys may contain forward slashes to indicate nested directories. * Then returned as output, each directory will always be represented as a separate object. * * @example * ```ts * { * 'test.txt': 'content', * 'sub-dir': { * 'file.txt': 'content', * 'nested-dir/file.txt': 'content', * }, * 'empty-dir': {}, * 'binary-file': Buffer.from([0, 1, 2]), * } * ``` * * @public */ type MockDirectoryContent = { [name in string]: MockDirectoryContent | string | Buffer | MockDirectoryContentCallback; }; /** * Options for {@link MockDirectory.content}. * * @public */ interface MockDirectoryContentOptions { /** * The path to read content from. Defaults to the root of the mock directory. * * An absolute path can also be provided, as long as it is a child path of the mock directory. */ path?: string; /** * Whether or not to return files as text rather than buffers. * * Defaults to checking the file extension against a list of known text extensions. */ shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean); } /** * A utility for creating a mock directory that is automatically cleaned up. * * @public */ interface MockDirectory { /** * The path to the root of the mock directory */ readonly path: string; /** * Resolves a path relative to the root of the mock directory. */ resolve(...paths: string[]): string; /** * Sets the content of the mock directory. This will remove any existing content. * * @example * ```ts * mockDir.setContent({ * 'test.txt': 'content', * 'sub-dir': { * 'file.txt': 'content', * 'nested-dir/file.txt': 'content', * }, * 'empty-dir': {}, * 'binary-file': Buffer.from([0, 1, 2]), * }); * ``` */ setContent(root: MockDirectoryContent): void; /** * Adds content of the mock directory. This will overwrite existing files. * * @example * ```ts * mockDir.addContent({ * 'test.txt': 'content', * 'sub-dir': { * 'file.txt': 'content', * 'nested-dir/file.txt': 'content', * }, * 'empty-dir': {}, * 'binary-file': Buffer.from([0, 1, 2]), * }); * ``` */ addContent(root: MockDirectoryContent): void; /** * Reads the content of the mock directory. * * @remarks * * Text files will be returned as strings, while binary files will be returned as buffers. * By default the file extension is used to determine whether a file should be read as text. * * @example * ```ts * expect(mockDir.content()).toEqual({ * 'test.txt': 'content', * 'sub-dir': { * 'file.txt': 'content', * 'nested-dir': { * 'file.txt': 'content', * }, * }, * 'empty-dir': {}, * 'binary-file': Buffer.from([0, 1, 2]), * }); * ``` */ content(options?: MockDirectoryContentOptions): MockDirectoryContent | undefined; /** * Clears the content of the mock directory, ensuring that the directory itself exists. */ clear(): void; /** * Removes the mock directory and all its contents. */ remove(): void; } /** * Options for {@link createMockDirectory}. * * @public */ interface CreateMockDirectoryOptions { /** * In addition to creating a temporary directory, also mock `os.tmpdir()` to * return the mock directory path until the end of the test suite. * * When this option is provided the `createMockDirectory` call must happen in * a scope where calling `afterAll` from Jest is allowed * * @returns */ mockOsTmpDir?: boolean; /** * Initializes the directory with the given content, see {@link MockDirectory.setContent}. */ content?: MockDirectoryContent; } /** * Creates a new temporary mock directory that will be removed after the tests have completed. * * @public * @remarks * * This method is intended to be called outside of any test, either at top-level or * within a `describe` block. It will call `afterAll` to make sure that the mock directory * is removed after the tests have run. * * @example * ```ts * describe('MySubject', () => { * const mockDir = createMockDirectory(); * * beforeEach(mockDir.clear); * * it('should work', () => { * // ... use mockDir * }) * }) * ``` */ declare function createMockDirectory(options?: CreateMockDirectoryOptions): MockDirectory; /** @public */ type ServiceMock<TService> = { factory: ServiceFactory<TService>; } & { [Key in keyof TService]: TService[Key] extends (...args: infer Args) => infer Return ? TService[Key] & jest.MockInstance<Return, Args> : TService[Key]; }; /** * Mock implementations of the core services, to be used in tests. * * @public * @remarks * * There are some variations among the services depending on what needs tests * might have, but overall there are three main usage patterns: * * 1. Creating an actual fake service instance, often with a simplified version * of functionality, by calling the mock service itself as a function. * * ```ts * // The function often accepts parameters that control its behavior * const foo = mockServices.foo(); * ``` * * 2. Creating a mock service, where all methods are replaced with jest mocks, by * calling the service's `mock` function. * * ```ts * // You can optionally supply a subset of its methods to implement * const foo = mockServices.foo.mock({ * someMethod: () => 'mocked result', * }); * // After exercising your test, you can make assertions on the mock: * expect(foo.someMethod).toHaveBeenCalledTimes(2); * expect(foo.otherMethod).toHaveBeenCalledWith(testData); * ``` * * 3. Creating a service factory that behaves similarly to the mock as per above. * * ```ts * await startTestBackend({ * features: [ * mockServices.foo.factory({ * someMethod: () => 'mocked result', * }) * ], * }); * ``` */ declare namespace mockServices { function rootConfig(options?: rootConfig.Options): RootConfigService & { update(options: { data: JsonObject; }): void; }; namespace rootConfig { type Options = { data?: JsonObject; }; const factory: (options?: Options | undefined) => ServiceFactory<RootConfigService, "root", "singleton">; const mock: (partialImpl?: Partial<RootConfigService> | undefined) => ServiceMock<RootConfigService>; } function rootLogger(options?: rootLogger.Options): RootLoggerService; namespace rootLogger { type Options = { level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; const factory: (options?: Options | undefined) => ServiceFactory<RootLoggerService, "root", "singleton">; const mock: (partialImpl?: Partial<RootLoggerService> | undefined) => ServiceMock<RootLoggerService>; } namespace auditor { const factory: () => ServiceFactory<_backstage_backend_plugin_api.AuditorService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.AuditorService> | undefined) => ServiceMock<_backstage_backend_plugin_api.AuditorService>; } function auth(options?: { pluginId?: string; disableDefaultAuthPolicy?: boolean; }): AuthService; namespace auth { const factory: () => ServiceFactory<AuthService, "plugin", "singleton">; const mock: (partialImpl?: Partial<AuthService> | undefined) => ServiceMock<AuthService>; } function discovery(): DiscoveryService; namespace discovery { const factory: () => ServiceFactory<DiscoveryService, "plugin", "singleton">; const mock: (partialImpl?: Partial<DiscoveryService> | undefined) => ServiceMock<DiscoveryService>; } /** * Creates a mock implementation of the `HttpAuthService`. * * By default all requests without credentials are treated as requests from * the default mock user principal. This behavior can be configured with the * `defaultCredentials` option. */ function httpAuth(options?: { pluginId?: string; /** * The default credentials to use if there are no credentials present in the * incoming request. * * By default all requests without credentials are treated as authenticated * as the default mock user as returned from `mockCredentials.user()`. */ defaultCredentials?: BackstageCredentials; }): HttpAuthService; namespace httpAuth { /** * Creates a mock service factory for the `HttpAuthService`. * * By default all requests without credentials are treated as requests from * the default mock user principal. This behavior can be configured with the * `defaultCredentials` option. */ const factory: (options?: { defaultCredentials?: BackstageCredentials; }) => ServiceFactory<HttpAuthService, "plugin", "singleton">; const mock: (partialImpl?: Partial<HttpAuthService> | undefined) => ServiceMock<HttpAuthService>; } /** * Creates a mock implementation of the `UserInfoService`. * * By default it extracts the user's entity ref from a user principal and * returns that as the only ownership entity ref, but this can be overridden * by passing in a custom set of user info. */ function userInfo(customInfo?: Partial<BackstageUserInfo>): UserInfoService; namespace userInfo { /** * Creates a mock service factory for the `UserInfoService`. * * By default it extracts the user's entity ref from a user principal and * returns that as the only ownership entity ref. */ const factory: () => ServiceFactory<UserInfoService, "plugin", "singleton">; const mock: (partialImpl?: Partial<UserInfoService> | undefined) => ServiceMock<UserInfoService>; } namespace cache { const factory: () => ServiceFactory<_backstage_backend_plugin_api.CacheService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.CacheService> | undefined) => ServiceMock<_backstage_backend_plugin_api.CacheService>; } /** * Creates a mock implementation of the * {@link @backstage/backend-plugin-api#coreServices.database}. Just returns * the given `knex` instance, which is useful in combination with the * {@link TestDatabases} facility. */ function database(options: { knex: Knex; migrations?: { skip?: boolean; }; }): DatabaseService; namespace database { /** * Creates a mock factory for the * {@link @backstage/backend-plugin-api#coreServices.database}. Just returns * the given `knex` instance if you supply one, which is useful in * combination with the {@link TestDatabases} facility. Otherwise, it * returns the regular default database factory which reads config settings. */ const factory: (options?: { knex: Knex; migrations?: { skip?: boolean; }; }) => ServiceFactory<DatabaseService, "plugin", "singleton">; /** * Creates a mock of the * {@link @backstage/backend-plugin-api#coreServices.database}, optionally * with some given method implementations. */ const mock: (partialImpl?: Partial<DatabaseService> | undefined) => ServiceMock<DatabaseService>; } namespace rootHealth { const factory: () => ServiceFactory<_backstage_backend_plugin_api.RootHealthService, "root", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.RootHealthService> | undefined) => ServiceMock<_backstage_backend_plugin_api.RootHealthService>; } namespace httpRouter { const factory: () => ServiceFactory<_backstage_backend_plugin_api.HttpRouterService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.HttpRouterService> | undefined) => ServiceMock<_backstage_backend_plugin_api.HttpRouterService>; } namespace rootHttpRouter { const factory: () => ServiceFactory<_backstage_backend_plugin_api.RootHttpRouterService, "root", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.RootHttpRouterService> | undefined) => ServiceMock<_backstage_backend_plugin_api.RootHttpRouterService>; } namespace lifecycle { const factory: () => ServiceFactory<_backstage_backend_plugin_api.LifecycleService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.LifecycleService> | undefined) => ServiceMock<_backstage_backend_plugin_api.LifecycleService>; } namespace logger { const factory: () => ServiceFactory<_backstage_backend_plugin_api.LoggerService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.LoggerService> | undefined) => ServiceMock<_backstage_backend_plugin_api.LoggerService>; } /** * Creates a functional mock implementation of the * {@link @backstage/backend-plugin-api#PermissionsService}. */ function permissions(options?: { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; }): PermissionsService; namespace permissions { /** * Creates a mock factory for the * {@link @backstage/backend-plugin-api#coreServices.permissions}. Just * returns the given `result` if you supply one. Otherwise, it returns the * regular default permissions factory. */ const factory: (options?: { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY; }) => ServiceFactory<PermissionsService, "plugin", "singleton">; /** * Creates a mock of the * {@link @backstage/backend-plugin-api#coreServices.permissions}, * optionally with some given method implementations. */ const mock: (partialImpl?: Partial<PermissionsService> | undefined) => ServiceMock<PermissionsService>; } namespace permissionsRegistry { const factory: () => ServiceFactory<_backstage_backend_plugin_api.PermissionsRegistryService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.PermissionsRegistryService> | undefined) => ServiceMock<_backstage_backend_plugin_api.PermissionsRegistryService>; } namespace rootLifecycle { const factory: () => ServiceFactory<_backstage_backend_plugin_api.RootLifecycleService, "root", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.RootLifecycleService> | undefined) => ServiceMock<_backstage_backend_plugin_api.RootLifecycleService>; } function scheduler(): SchedulerService; namespace scheduler { const factory: (options?: { skipTaskRunOnStartup?: boolean; includeManualTasksOnStartup?: boolean; includeInitialDelayedTasksOnStartup?: boolean; }) => ServiceFactory<SchedulerService, "plugin", "singleton">; const mock: (partialImpl?: Partial<SchedulerService> | undefined) => ServiceMock<SchedulerService>; } namespace urlReader { const factory: () => ServiceFactory<_backstage_backend_plugin_api.UrlReaderService, "plugin", "singleton">; const mock: (partialImpl?: Partial<_backstage_backend_plugin_api.UrlReaderService> | undefined) => ServiceMock<_backstage_backend_plugin_api.UrlReaderService>; } /** * Creates a functional mock implementation of the * {@link @backstage/backend-events-node#eventsServiceRef}. */ function events(): EventsService; namespace events { /** * Creates a functional mock factory for the * {@link @backstage/backend-events-node#eventsServiceRef}. */ const factory: () => ServiceFactory<EventsService, "plugin", "singleton">; /** * Creates a mock of the * {@link @backstage/backend-events-node#eventsServiceRef}, optionally * with some given method implementations. */ const mock: (partialImpl?: Partial<EventsService> | undefined) => ServiceMock<EventsService>; } function rootInstanceMetadata(): RootInstanceMetadataService; namespace rootInstanceMetadata { const mock: (partialImpl?: Partial<RootInstanceMetadataService> | undefined) => ServiceMock<RootInstanceMetadataService>; const factory: () => ServiceFactory<RootInstanceMetadataService, "root", "singleton">; } } /** * @public */ declare namespace mockCredentials { /** * Creates a mocked credentials object for a unauthenticated principal. */ function none(): BackstageCredentials<BackstageNonePrincipal>; /** * Utilities related to none credentials. */ namespace none { /** * Returns an authorization header that translates to unauthenticated * credentials. * * This is useful when one wants to explicitly test unauthenticated requests * while still using the default behavior of the mock HttpAuthService where * it defaults to user credentials. */ function header(): string; } /** * Creates a mocked credentials object for a user principal. * * The default user entity reference is 'user:default/mock'. */ function user(userEntityRef?: string, options?: { actor?: { subject: string; }; }): BackstageCredentials<BackstageUserPrincipal>; /** * Utilities related to user credentials. */ namespace user { /** * Creates a mocked user token. If a payload is provided it will be encoded * into the token and forwarded to the credentials object when authenticated * by the mock auth service. */ function token(userEntityRef?: string, options?: { actor?: { subject: string; }; }): string; /** * Returns an authorization header with a mocked user token. If a payload is * provided it will be encoded into the token and forwarded to the * credentials object when authenticated by the mock auth service. */ function header(userEntityRef?: string): string; function invalidToken(): string; function invalidHeader(): string; } /** * Creates a mocked credentials object for a user principal with limited * access. * * The default user entity reference is 'user:default/mock'. */ function limitedUser(userEntityRef?: string): BackstageCredentials<BackstageUserPrincipal>; /** * Utilities related to limited user credentials. */ namespace limitedUser { /** * Creates a mocked limited user token. If a payload is provided it will be * encoded into the token and forwarded to the credentials object when * authenticated by the mock auth service. */ function token(userEntityRef?: string): string; /** * Returns an authorization header with a mocked limited user token. If a * payload is provided it will be encoded into the token and forwarded to * the credentials object when authenticated by the mock auth service. */ function cookie(userEntityRef?: string): string; function invalidToken(): string; function invalidCookie(): string; } /** * Creates a mocked credentials object for a service principal. * * The default subject is 'external:test-service', and no access restrictions. */ function service(subject?: string, accessRestrictions?: BackstagePrincipalAccessRestrictions): BackstageCredentials<BackstageServicePrincipal>; /** * Utilities related to service credentials. */ namespace service { /** * Options for the creation of mock service tokens. */ type TokenOptions = { onBehalfOf: BackstageCredentials; targetPluginId: string; }; /** * Creates a mocked service token. The provided options will be encoded into * the token and forwarded to the credentials object when authenticated by * the mock auth service. */ function token(options?: TokenOptions): string; /** * Returns an authorization header with a mocked service token. The provided * options will be encoded into the token and forwarded to the credentials * object when authenticated by the mock auth service. */ function header(options?: TokenOptions): string; function invalidToken(): string; function invalidHeader(): string; } } /** * Options for {@link ServiceFactoryTester}. * @public */ interface ServiceFactoryTesterOptions { /** * Additional service factories to make available as dependencies. * * @remarks * * If a service factory is provided for a service that already has a default * implementation, the provided factory will override the default. */ dependencies?: Array<ServiceFactory>; } /** * A utility to help test service factories in isolation. * * @public */ declare class ServiceFactoryTester<TService, TScope extends 'root' | 'plugin', TInstances extends 'singleton' | 'multiton' = 'singleton'> { #private; /** * Creates a new {@link ServiceFactoryTester} used to test the provided subject. * * @param subject - The service factory to test. * @param options - Additional options * @returns A new tester instance for the provided subject. */ static from<TService, TScope extends 'root' | 'plugin', TInstances extends 'singleton' | 'multiton' = 'singleton'>(subject: ServiceFactory<TService, TScope, TInstances>, options?: ServiceFactoryTesterOptions): ServiceFactoryTester<TService, TScope, TInstances>; private constructor(); /** * Returns the service instance for the subject. * * @remarks * * If the subject is a plugin scoped service factory a plugin ID * can be provided to instantiate the service for a specific plugin. * * By default the plugin ID 'test' is used. */ getSubject(...args: 'root' extends TScope ? [] : [pluginId?: string]): Promise<TInstances extends 'multiton' ? TService[] : TService>; /** * Return the service instance for any of the provided dependencies or built-in services. * * @remarks * * A plugin ID can optionally be provided for plugin scoped services, otherwise the plugin ID 'test' is used. */ getService<TGetService, TGetScope extends 'root' | 'plugin', TGetInstances extends 'singleton' | 'multiton' = 'singleton'>(service: ServiceRef<TGetService, TGetScope, TGetInstances>, ...args: 'root' extends TGetScope ? [] : [pluginId?: string]): Promise<TGetInstances extends 'multiton' ? TGetService[] : TGetService>; } /** @public */ interface TestBackendOptions<TExtensionPoints extends any[]> { extensionPoints?: readonly [ ...{ [index in keyof TExtensionPoints]: [ ExtensionPoint<TExtensionPoints[index]>, Partial<TExtensionPoints[index]> ]; } ]; features?: Array<BackendFeature | Promise<{ default: BackendFeature; }>>; } /** @public */ interface TestBackend extends Backend { /** * Provides access to the underling HTTP server for use with utilities * such as `supertest`. * * If the root http router service has been replaced, this will throw an error. */ readonly server: ExtendedHttpServer; } /** @public */ declare function startTestBackend<TExtensionPoints extends any[]>(options: TestBackendOptions<TExtensionPoints>): Promise<TestBackend>; /** * A mock for error handler middleware that can be used in router tests. * @public * * @example * ```ts * const app = express(); * app.use(mockErrorHandler()); * ``` */ declare function mockErrorHandler(_options?: {}, ..._args: never[]): express.ErrorRequestHandler<express_serve_static_core.ParamsDictionary, any, any, qs.ParsedQs, Record<string, any>>; export { ServiceFactoryTester, TestCaches, TestDatabases, createMockDirectory, mockCredentials, mockErrorHandler, mockServices, registerMswTestHooks, startTestBackend }; export type { CreateMockDirectoryOptions, MockDirectory, MockDirectoryContent, MockDirectoryContentCallback, MockDirectoryContentCallbackContext, MockDirectoryContentOptions, ServiceFactoryTesterOptions, ServiceMock, TestBackend, TestBackendOptions, TestCacheId, TestDatabaseId };