diff --git a/packages/react-native/src/BacktraceClient.ts b/packages/react-native/src/BacktraceClient.ts index f5b589a3..7892fbe1 100644 --- a/packages/react-native/src/BacktraceClient.ts +++ b/packages/react-native/src/BacktraceClient.ts @@ -15,11 +15,12 @@ import { BacktraceClientBuilder } from './builder/BacktraceClientBuilder'; import type { BacktraceClientSetup } from './builder/BacktraceClientSetup'; import { version } from './common/platformHelper'; import { CrashReporter } from './crashReporter/CrashReporter'; +import { assertDatabasePath } from './database/utils'; import { generateUnhandledExceptionHandler } from './handlers'; import { type ExceptionHandler } from './handlers/ExceptionHandler'; import { ReactNativeRequestHandler } from './ReactNativeRequestHandler'; import { ReactStackTraceConverter } from './ReactStackTraceConverter'; -import { type FileSystem } from './storage/FileSystem'; +import { ReactNativePathBacktraceStorageFactory, type BacktraceStorageModule } from './storage'; export class BacktraceClient extends BacktraceCoreClient { private readonly _crashReporter?: CrashReporter; @@ -33,7 +34,21 @@ export class BacktraceClient extends BacktraceCoreClient return NativeModules.BacktraceDirectoryProvider?.applicationDirectory() ?? ''; } + protected get databaseRnStorage() { + return this.databaseStorage as BacktraceStorageModule | undefined; + } + constructor(clientSetup: BacktraceClientSetup) { + const storageFactory = clientSetup.storageFactory ?? new ReactNativePathBacktraceStorageFactory(); + const storage = + clientSetup.database?.storage ?? + (clientSetup.options.database?.enable + ? storageFactory.create({ + path: assertDatabasePath(clientSetup.options.database?.path), + createDirectory: clientSetup.options.database.createDatabaseDirectory, + }) + : undefined); + super({ sdkOptions: { agent: '@backtrace/react-native', @@ -48,14 +63,9 @@ export class BacktraceClient extends BacktraceCoreClient ...clientSetup, }); - const fileSystem = clientSetup.fileSystem as FileSystem; - if (!fileSystem) { - return; - } - const breadcrumbsManager = this.modules.get(BreadcrumbsManager); - if (breadcrumbsManager && this.sessionFiles) { - breadcrumbsManager.setStorage(FileBreadcrumbsStorage.factory(this.sessionFiles, fileSystem)); + if (breadcrumbsManager && this.sessionFiles && storage) { + breadcrumbsManager.setStorage(FileBreadcrumbsStorage.factory(this.sessionFiles, storage)); } this.attributeManager.attributeEvents.on( @@ -137,21 +147,20 @@ export class BacktraceClient extends BacktraceCoreClient return; } - const fileSystem = this.fileSystem; - if (!fileSystem) { + const storage = this.databaseRnStorage; + if (!storage) { return; } const submissionUrl = SubmissionUrlInformation.toJsonReportSubmissionUrl(this.options.url); - const crashReporter = new CrashReporter(fileSystem); + const crashReporter = new CrashReporter(storage); crashReporter.initialize( Platform.select({ ios: SubmissionUrlInformation.toPlCrashReporterSubmissionUrl(submissionUrl), android: SubmissionUrlInformation.toMinidumpSubmissionUrl(submissionUrl), default: submissionUrl, }), - this.options.database.path, this.attributeManager.get('scoped').attributes, this.attachments, ); diff --git a/packages/react-native/src/BacktraceConfiguration.ts b/packages/react-native/src/BacktraceConfiguration.ts index 46f3ba97..d218c3c0 100644 --- a/packages/react-native/src/BacktraceConfiguration.ts +++ b/packages/react-native/src/BacktraceConfiguration.ts @@ -1,2 +1,29 @@ -import { type BacktraceConfiguration as SdkConfiguration } from '@backtrace/sdk-core'; -export interface BacktraceConfiguration extends SdkConfiguration {} +import { + type DisabledBacktraceDatabaseConfiguration as CoreDisabledBacktraceDatabaseConfiguration, + type EnabledBacktraceDatabaseConfiguration as CoreEnabledBacktraceDatabaseConfiguration, + type BacktraceConfiguration as SdkConfiguration, +} from '@backtrace/sdk-core'; + +export interface EnabledBacktraceDatabaseConfiguration extends CoreEnabledBacktraceDatabaseConfiguration { + /** + * Path where the SDK can store data. + */ + path: string; + /** + * Determine if the directory should be auto created by the SDK. + * @default true + */ + createDatabaseDirectory?: boolean; +} + +export interface DisabledBacktraceDatabaseConfiguration + extends CoreDisabledBacktraceDatabaseConfiguration, + Omit, 'enable'> {} + +export type BacktraceDatabaseConfiguration = + | EnabledBacktraceDatabaseConfiguration + | DisabledBacktraceDatabaseConfiguration; + +export interface BacktraceConfiguration extends Omit { + database: BacktraceDatabaseConfiguration; +} diff --git a/packages/react-native/src/attachment/BacktraceFileAttachment.ts b/packages/react-native/src/attachment/BacktraceFileAttachment.ts index ff939731..d41e8185 100644 --- a/packages/react-native/src/attachment/BacktraceFileAttachment.ts +++ b/packages/react-native/src/attachment/BacktraceFileAttachment.ts @@ -1,14 +1,15 @@ -import { type BacktraceFileAttachment as CoreBacktraceFileAttachment } from '@backtrace/sdk-core'; -import { Platform } from 'react-native'; -import { type FileSystem } from '../storage/'; +import { type BacktraceAttachment } from '@backtrace/sdk-core'; +import { NativeModules, Platform } from 'react-native'; +import type { ReactNativeFileProvider } from '../storage'; import { type FileLocation } from '../types/FileLocation'; -export class BacktraceFileAttachment implements CoreBacktraceFileAttachment { +export class BacktraceFileAttachment implements BacktraceAttachment { + private readonly _fileSystemProvider: ReactNativeFileProvider = NativeModules.BacktraceFileSystemProvider; + public readonly name: string; public readonly mimeType: string; private readonly _uploadUri: string; constructor( - private readonly _fileSystemProvider: FileSystem, public readonly filePath: string, name?: string, mimeType?: string, @@ -20,7 +21,6 @@ export class BacktraceFileAttachment implements CoreBacktraceFileAttachment session.getFileName(FileBreadcrumbsStorage.getFileName(n)), }); @@ -72,15 +74,16 @@ export class FileBreadcrumbsStorage implements BreadcrumbsStorage { this._destinationWriter = this._destinationStream.getWriter(); } - public static factory(session: SessionFiles, fileSystem: FileSystem): BreadcrumbsStorageFactory { - return ({ limits }) => new FileBreadcrumbsStorage(session, fileSystem, limits); + public static factory( + session: SessionFiles, + storage: BacktraceStorage & BacktraceSyncStorage & BacktraceStreamStorage, + ): BreadcrumbsStorageFactory { + return ({ limits }) => new FileBreadcrumbsStorage(session, storage, limits); } public getAttachments(): BacktraceFileAttachment[] { const files = [...this._sink.files].map((f) => f.path); - return files.map( - (f, i) => new BacktraceFileAttachment(this._fileSystem, f, `bt-breadcrumbs-${i}`, 'application/json'), - ); + return files.map((f, i) => new BacktraceFileAttachment(f, `bt-breadcrumbs-${i}`, 'application/json')); } public getAttachmentProviders(): BacktraceAttachmentProvider[] { diff --git a/packages/react-native/src/builder/BacktraceClientBuilder.ts b/packages/react-native/src/builder/BacktraceClientBuilder.ts index 882dc2bb..302795e3 100644 --- a/packages/react-native/src/builder/BacktraceClientBuilder.ts +++ b/packages/react-native/src/builder/BacktraceClientBuilder.ts @@ -7,7 +7,8 @@ import { AppStateBreadcrumbSubscriber } from '../breadcrumbs/events/AppStateBrea import { DimensionChangeBreadcrumbSubscriber } from '../breadcrumbs/events/DimensionChangeBreadcrumbSubscriber'; import { WebRequestEventSubscriber } from '../breadcrumbs/events/WebRequestEventSubscriber'; import { DebuggerHelper } from '../common/DebuggerHelper'; -import { ReactNativeFileSystem } from '../storage'; +import { ReactNativePathBacktraceStorageFactory } from '../storage'; +import type { BacktraceStorageModuleFactory } from '../storage/PathBacktraceStorageFactory'; import type { BacktraceClientSetup } from './BacktraceClientSetup'; export class BacktraceClientBuilder extends BacktraceCoreClientBuilder { @@ -45,14 +46,14 @@ export class BacktraceClientBuilder extends BacktraceCoreClientBuilder {} +type BaseCoreClientSetup = PartialCoreClientSetup<'sdkOptions' | 'requestHandler' | 'database', BacktraceConfiguration>; + +export interface BacktraceClientSetup extends BaseCoreClientSetup { + readonly storageFactory?: BacktraceStorageModuleFactory; + readonly database?: Omit, 'storage'> & { + readonly storage?: BacktraceStorageModule; + }; +} diff --git a/packages/react-native/src/crashReporter/CrashReporter.ts b/packages/react-native/src/crashReporter/CrashReporter.ts index ab9a9a99..0cdf8079 100644 --- a/packages/react-native/src/crashReporter/CrashReporter.ts +++ b/packages/react-native/src/crashReporter/CrashReporter.ts @@ -1,14 +1,15 @@ -import { type AttributeType, type BacktraceAttachment, type FileSystem } from '@backtrace/sdk-core'; +import { type AttributeType, type BacktraceAttachment } from '@backtrace/sdk-core'; import { NativeModules } from 'react-native'; import { BacktraceFileAttachment } from '../attachment/BacktraceFileAttachment'; import { DebuggerHelper } from '../common/DebuggerHelper'; +import type { BacktraceDirectorySyncStorage, BacktracePathStorage } from '../storage'; export class CrashReporter { private static readonly BacktraceReactNative = NativeModules.BacktraceReactNative; private _enabled = false; - constructor(private readonly _fileSystem: FileSystem) {} + constructor(private readonly _storage: BacktraceDirectorySyncStorage & BacktracePathStorage) {} /** * Determines if the crash reporting solution was already initialized. @@ -17,7 +18,6 @@ export class CrashReporter { public initialize( submissionUrl: string, - databasePath: string, attributes: Record, attachments: readonly BacktraceAttachment[], ): boolean { @@ -33,9 +33,9 @@ export class CrashReporter { return false; } - const nativeDatabasePath = `${databasePath}/native`; - this._fileSystem.createDirSync(nativeDatabasePath); + this._storage.createDirSync('native'); + const nativeDatabasePath = this._storage.getFullPath('native'); CrashReporter.BacktraceReactNative.initialize( submissionUrl, nativeDatabasePath, diff --git a/packages/react-native/src/database/utils.ts b/packages/react-native/src/database/utils.ts new file mode 100644 index 00000000..8ac2d567 --- /dev/null +++ b/packages/react-native/src/database/utils.ts @@ -0,0 +1,8 @@ +export function assertDatabasePath(path: string) { + if (!path) { + throw new Error( + 'Missing mandatory path to the database. Please define the database.path option in the configuration.', + ); + } + return path; +} diff --git a/packages/react-native/src/storage/FileChunkSink.ts b/packages/react-native/src/storage/FileChunkSink.ts index 32d11b36..c2935791 100644 --- a/packages/react-native/src/storage/FileChunkSink.ts +++ b/packages/react-native/src/storage/FileChunkSink.ts @@ -1,5 +1,6 @@ +import type { BacktraceStorage } from '@backtrace/sdk-core'; import type { ChunkSink } from './Chunkifier'; -import type { FileSystem } from './FileSystem'; +import type { BacktraceStreamStorage } from './storage'; import type { FileWritableStream } from './StreamWriter'; interface FileChunkSinkOptions { @@ -16,7 +17,7 @@ interface FileChunkSinkOptions { /** * File system to use. */ - readonly fs: FileSystem; + readonly storage: BacktraceStorage & BacktraceStreamStorage; } /** @@ -43,7 +44,7 @@ export class FileChunkSink { // Fail silently here, there's not much we can do about this }) .finally(() => - _options.fs.unlink(stream.path).catch(() => { + _options.storage.remove(stream.path).catch(() => { // Fail silently here, there's not much we can do about this }), ); @@ -63,7 +64,7 @@ export class FileChunkSink { private createStream(n: number) { const path = this._options.file(n); - return this._options.fs.createWriteStream(path); + return this._options.storage.createWriteStream(path); } } diff --git a/packages/react-native/src/storage/FileSystem.ts b/packages/react-native/src/storage/FileSystem.ts deleted file mode 100644 index 61329edc..00000000 --- a/packages/react-native/src/storage/FileSystem.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type FileSystem as CoreFileSystem } from '@backtrace/sdk-core'; -import type { FileWritableStream } from './StreamWriter'; -export interface FileSystem extends CoreFileSystem { - copy(sourceFile: string, destinationFile: string): Promise; - copySync(sourceFile: string, destinationFile: string): boolean; - applicationDirectory(): string; - createWriteStream(path: string): FileWritableStream; -} diff --git a/packages/react-native/src/storage/PathBacktraceStorageFactory.ts b/packages/react-native/src/storage/PathBacktraceStorageFactory.ts new file mode 100644 index 00000000..66f840af --- /dev/null +++ b/packages/react-native/src/storage/PathBacktraceStorageFactory.ts @@ -0,0 +1,5 @@ +import { type BacktraceStorageModule, type BacktraceStorageModuleOptions } from './storage.js'; + +export interface BacktraceStorageModuleFactory { + create(options: BacktraceStorageModuleOptions): BacktraceStorageModule; +} diff --git a/packages/react-native/src/storage/ReactNativeBacktraceStorage.ts b/packages/react-native/src/storage/ReactNativeBacktraceStorage.ts new file mode 100644 index 00000000..6fd61a2f --- /dev/null +++ b/packages/react-native/src/storage/ReactNativeBacktraceStorage.ts @@ -0,0 +1,160 @@ +import type { BacktraceModule } from '@backtrace/sdk-core'; +import { NativeModules } from 'react-native'; +import type { BacktraceStorageModuleFactory } from './PathBacktraceStorageFactory'; +import { type ReactNativeDirectoryProvider } from './ReactNativeDirectoryProvider'; +import { type ReactNativeFileProvider } from './ReactNativeFileProvider'; +import { type BacktraceStorageModule, type BacktraceStorageModuleOptions } from './storage'; +import { FileWritableStream, type StreamWriter } from './StreamWriter'; + +export class ReactNativeBacktraceStorage implements BacktraceStorageModule, BacktraceModule { + private readonly _fileSystemProvider: ReactNativeFileProvider = NativeModules.BacktraceFileSystemProvider; + private readonly _directoryProvider: ReactNativeDirectoryProvider = NativeModules.BacktraceDirectoryProvider; + private readonly _streamWriter: StreamWriter = NativeModules.StreamWriter; + private readonly _path: string; + private readonly _createDirectory: boolean; + + constructor(options: BacktraceStorageModuleOptions) { + if (!this._fileSystemProvider) { + throw new Error(`Cannot setup native binding. Missing file system provider`); + } + + if (!this._directoryProvider) { + throw new Error(`Cannot setup native binding. Missing directory provider`); + } + + if (!this._streamWriter) { + throw new Error(`Cannot setup native binding. Missing AlternatingFileWriter`); + } + + // Remove trailing slashes and add a single slash at the end + this._path = options.path.replaceAll(/\/+$/g, '') + '/'; + this._createDirectory = !!options.createDirectory; + } + + public streamWriter: StreamWriter = this._streamWriter; + + public initialize(): void { + if (this._createDirectory) { + this.createDirSync(this._path); + } + } + + public createDirSync(dir: string): boolean { + try { + this._directoryProvider.createDirSync(dir); + return true; + } catch { + return false; + } + } + + public getFullPath(path: string): string { + return this.resolvePath(path); + } + + public async get(key: string): Promise { + try { + return await this._fileSystemProvider.readFile(this.resolvePath(key)); + } catch { + return undefined; + } + } + + public getSync(key: string): string | undefined { + try { + return this._fileSystemProvider.readFileSync(this.resolvePath(key)); + } catch { + return undefined; + } + } + + public async set(key: string, content: string): Promise { + try { + await this._fileSystemProvider.writeFile(this.resolvePath(key), content); + return true; + } catch { + return false; + } + } + + public setSync(key: string, content: string): boolean { + try { + this._fileSystemProvider.writeFileSync(this.resolvePath(key), content); + return true; + } catch { + return false; + } + } + + public async remove(key: string): Promise { + try { + await this._fileSystemProvider.unlink(this.resolvePath(key)); + return true; + } catch { + return false; + } + } + + public removeSync(key: string): boolean { + try { + this._fileSystemProvider.unlinkSync(this.resolvePath(key)); + return true; + } catch { + return false; + } + } + + public async has(key: string): Promise { + try { + return await this._fileSystemProvider.exists(this.resolvePath(key)); + } catch { + return false; + } + } + + public hasSync(key: string): boolean { + try { + return this._fileSystemProvider.existsSync(this.resolvePath(key)); + } catch { + return false; + } + } + + public async *keys(): AsyncGenerator { + try { + for (const key in this._directoryProvider.readDir(this._path)) { + yield key; + } + } catch { + return; + } + } + + public *keysSync(): Generator { + try { + for (const key in this._directoryProvider.readDir(this._path)) { + yield key; + } + } catch { + return; + } + } + + public createWriteStream(key: string): FileWritableStream { + return new FileWritableStream(this.resolvePath(key), this.streamWriter); + } + + protected resolvePath(key: string) { + if (key.startsWith('/')) { + return key; + } + + return this._path + key; + } +} + +export class ReactNativePathBacktraceStorageFactory implements BacktraceStorageModuleFactory { + public create(options: BacktraceStorageModuleOptions): BacktraceStorageModule { + return new ReactNativeBacktraceStorage(options); + } +} diff --git a/packages/react-native/src/storage/ReactNativeFileSystem.ts b/packages/react-native/src/storage/ReactNativeFileSystem.ts deleted file mode 100644 index f9c0b604..00000000 --- a/packages/react-native/src/storage/ReactNativeFileSystem.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { type BacktraceAttachment } from '@backtrace/sdk-core'; -import { NativeModules } from 'react-native'; -import { BacktraceFileAttachment } from '../attachment/BacktraceFileAttachment'; -import { type FileSystem } from './FileSystem'; -import { type ReactNativeDirectoryProvider } from './ReactNativeDirectoryProvider'; -import { type ReactNativeFileProvider } from './ReactNativeFileProvider'; -import { FileWritableStream, type StreamWriter } from './StreamWriter'; -export class ReactNativeFileSystem implements FileSystem { - private readonly _fileSystemProvider: ReactNativeFileProvider = NativeModules.BacktraceFileSystemProvider; - private readonly _directoryProvider: ReactNativeDirectoryProvider = NativeModules.BacktraceDirectoryProvider; - private readonly _streamWriter: StreamWriter = NativeModules.StreamWriter; - - constructor() { - if (!this._fileSystemProvider) { - throw new Error(`Cannot setup native binding. Missing file system provider`); - } - - if (!this._directoryProvider) { - throw new Error(`Cannot setup native binding. Missing directory provider`); - } - - if (!this._streamWriter) { - throw new Error(`Cannot setup native binding. Missing AlternatingFileWriter`); - } - } - - public streamWriter: StreamWriter = this._streamWriter; - - public applicationDirectory(): string { - return this._directoryProvider.applicationDirectory(); - } - - public readDir(dir: string): Promise { - return this._directoryProvider.readDir(dir); - } - - public readDirSync(dir: string): string[] { - return this._directoryProvider.readDirSync(dir); - } - - public createDir(dir: string): Promise { - return this._directoryProvider.createDir(dir); - } - - public createDirSync(dir: string): void { - return this._directoryProvider.createDirSync(dir); - } - - public readFile(path: string): Promise { - return this._fileSystemProvider.readFile(path); - } - - public readFileSync(path: string): string { - return this._fileSystemProvider.readFileSync(path); - } - - public writeFile(path: string, content: string): Promise { - return this._fileSystemProvider.writeFile(path, content); - } - - public writeFileSync(path: string, content: string): void { - return this._fileSystemProvider.writeFileSync(path, content); - } - - public unlink(path: string): Promise { - return this._fileSystemProvider.unlink(path); - } - - public unlinkSync(path: string): void { - return this._fileSystemProvider.unlinkSync(path); - } - - public exists(path: string): Promise { - return this._fileSystemProvider.exists(path); - } - - public existsSync(path: string): boolean { - return this._fileSystemProvider.existsSync(path); - } - - public copy(sourceFile: string, destinationFile: string): Promise { - return this._fileSystemProvider.copy(sourceFile, destinationFile); - } - - public copySync(sourceFile: string, destinationFile: string): boolean { - return this._fileSystemProvider.copySync(sourceFile, destinationFile); - } - - public createAttachment(path: string, name?: string | undefined): BacktraceAttachment { - return new BacktraceFileAttachment(this, path, name); - } - - public createWriteStream(path: string): FileWritableStream { - return new FileWritableStream(path, this.streamWriter); - } -} diff --git a/packages/react-native/src/storage/index.ts b/packages/react-native/src/storage/index.ts index 0682f995..b70e2ebd 100644 --- a/packages/react-native/src/storage/index.ts +++ b/packages/react-native/src/storage/index.ts @@ -1,5 +1,5 @@ -export * from './FileSystem'; +export * from './ReactNativeBacktraceStorage'; export * from './ReactNativeDirectoryProvider'; export * from './ReactNativeFileProvider'; -export * from './ReactNativeFileSystem'; +export * from './storage'; export * from './StreamWriter'; diff --git a/packages/react-native/src/storage/storage.ts b/packages/react-native/src/storage/storage.ts new file mode 100644 index 00000000..1ecf1828 --- /dev/null +++ b/packages/react-native/src/storage/storage.ts @@ -0,0 +1,25 @@ +import type { BacktraceStorageModule as CoreBacktraceStorageModule } from '@backtrace/sdk-core'; +import type { BacktraceConfiguration } from '../BacktraceConfiguration'; +import type { FileWritableStream } from './StreamWriter'; + +export interface BacktraceStreamStorage { + createWriteStream(key: string): FileWritableStream; +} + +export interface BacktraceDirectorySyncStorage { + createDirSync(dir: string): boolean; +} + +export interface BacktracePathStorage { + getFullPath(path: string): string; +} + +export type BacktraceStorageModule = CoreBacktraceStorageModule & + BacktraceStreamStorage & + BacktraceDirectorySyncStorage & + BacktracePathStorage; + +export interface BacktraceStorageModuleOptions { + readonly path: string; + readonly createDirectory?: boolean; +} diff --git a/packages/react-native/tests/_mocks/fileSystem.ts b/packages/react-native/tests/_mocks/fileSystem.ts index 5a747f5d..56ab2bbb 100644 --- a/packages/react-native/tests/_mocks/fileSystem.ts +++ b/packages/react-native/tests/_mocks/fileSystem.ts @@ -1,36 +1,26 @@ -import { MockedFileSystem, mockFileSystem } from '@backtrace/sdk-core/tests/_mocks/fileSystem'; -import path from 'path'; +import { MockedBacktraceStorage, mockBacktraceStorage } from '@backtrace/sdk-core/tests/_mocks/storage'; import { WritableStream } from 'web-streams-polyfill'; -import { FileSystem } from '../../src/storage/FileSystem'; +import { BacktraceStorageModule } from '../../src'; -export function mockStreamFileSystem(files?: Record): MockedFileSystem { - const fs = mockFileSystem(files); +export function mockStreamFileSystem( + files?: Record, +): MockedBacktraceStorage> { + const fs = mockBacktraceStorage(files); return { ...fs, - copy: jest.fn().mockImplementation((sourceFile, destinationFile) => { - fs.files[path.resolve(destinationFile)] = fs.files[path.resolve(sourceFile)]; - return Promise.resolve(true); - }), - - copySync: jest.fn().mockImplementation((sourceFile, destinationFile) => { - fs.files[path.resolve(destinationFile)] = fs.files[path.resolve(sourceFile)]; - return true; - }), + createDirSync: jest.fn().mockReturnValue(true), - applicationDirectory: jest.fn().mockImplementation(() => { - return '/'; - }), + getFullPath: jest.fn().mockImplementation((v) => v), createWriteStream: jest.fn().mockImplementation((p: string) => { const writable = new WritableStream({ write(str) { - const fullPath = path.resolve(p); - if (!fs.files[fullPath]) { - fs.files[fullPath] = str; + if (!fs.files[p]) { + fs.files[p] = str; } else { - fs.files[fullPath] += str; + fs.files[p] += str; } }, }); diff --git a/packages/react-native/tests/storage/FileBreadcrumbsStorage.spec.ts b/packages/react-native/tests/storage/FileBreadcrumbsStorage.spec.ts index 63a194db..190bffbd 100644 --- a/packages/react-native/tests/storage/FileBreadcrumbsStorage.spec.ts +++ b/packages/react-native/tests/storage/FileBreadcrumbsStorage.spec.ts @@ -1,23 +1,28 @@ import { Breadcrumb, BreadcrumbLogLevel, BreadcrumbType, RawBreadcrumb, SessionFiles } from '@backtrace/sdk-core'; -import { MockedFileSystem } from '@backtrace/sdk-core/tests/_mocks/fileSystem'; +import { MockedBacktraceStorage } from '@backtrace/sdk-core/tests/_mocks/storage'; import assert from 'assert'; import { promisify } from 'util'; +import { BacktraceStorageModule } from '../../src'; import { FileBreadcrumbsStorage } from '../../src/breadcrumbs/FileBreadcrumbsStorage'; -import { FileSystem } from '../../src/storage/FileSystem'; import { FileLocation } from '../../src/types/FileLocation'; import { mockStreamFileSystem } from '../_mocks/fileSystem'; -async function loadBreadcrumbs(fs: MockedFileSystem, location: FileLocation): Promise { - return (await fs.readFile(location.filepath)) - .split('\n') - .filter((n) => !!n) - .map((x) => { - try { - return JSON.parse(x); - } catch (err) { - throw new Error(`failed to parse "${x}": ${err}`); - } - }); +async function loadBreadcrumbs( + fs: MockedBacktraceStorage>, + location: FileLocation, +): Promise { + return ( + (await fs.get(location.filepath)) + ?.split('\n') + .filter((n) => !!n) + .map((x) => { + try { + return JSON.parse(x); + } catch (err) { + throw new Error(`failed to parse "${x}": ${err}`); + } + }) ?? [] + ); } const nextTick = promisify(process.nextTick); @@ -25,7 +30,7 @@ const nextTick = promisify(process.nextTick); describe('FileBreadcrumbsStorage', () => { it('should return added breadcrumbs', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { @@ -99,7 +104,7 @@ describe('FileBreadcrumbsStorage', () => { it('should return added breadcrumbs in two attachments', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { @@ -181,7 +186,7 @@ describe('FileBreadcrumbsStorage', () => { it('should return no more than maximumBreadcrumbs breadcrumbs', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { @@ -253,7 +258,7 @@ describe('FileBreadcrumbsStorage', () => { it('should return breadcrumbs up to the json size', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { @@ -322,7 +327,7 @@ describe('FileBreadcrumbsStorage', () => { it('should return attachments with a valid name from getAttachments', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { @@ -366,7 +371,7 @@ describe('FileBreadcrumbsStorage', () => { it('should return attachments with a valid name from getAttachmentProviders', async () => { const fs = mockStreamFileSystem(); - const session = new SessionFiles(fs, '.', 'sessionId'); + const session = new SessionFiles(fs, 'sessionId'); const breadcrumbs: RawBreadcrumb[] = [ { diff --git a/packages/react-native/tests/storage/fileChunkSink.spec.ts b/packages/react-native/tests/storage/fileChunkSink.spec.ts index 8fd302cf..5c9c667c 100644 --- a/packages/react-native/tests/storage/fileChunkSink.spec.ts +++ b/packages/react-native/tests/storage/fileChunkSink.spec.ts @@ -1,4 +1,3 @@ -import path from 'path'; import { WritableStream } from 'web-streams-polyfill'; import { FileChunkSink } from '../../src/storage/FileChunkSink'; import { mockStreamFileSystem } from '../_mocks/fileSystem'; @@ -15,18 +14,17 @@ function sortString(a: string, b: string) { describe('fileChunkSink', () => { it('should create a filestream with name from filename', async () => { - const fs = mockStreamFileSystem(); + const storage = mockStreamFileSystem(); const filename = 'abc'; - const sink = new FileChunkSink({ file: () => filename, maxFiles: Infinity, fs }); + const sink = new FileChunkSink({ file: () => filename, maxFiles: Infinity, storage }); const stream = sink.getSink()(0); expect(stream.path).toEqual(filename); }); it('should create a filestream each time it is called', async () => { - const fs = mockStreamFileSystem(); - const dir = 'test'; - const sink = new FileChunkSink({ file: (n) => path.join(dir, n.toString()), maxFiles: Infinity, fs }); + const storage = mockStreamFileSystem(); + const sink = new FileChunkSink({ file: (n) => n.toString(), maxFiles: Infinity, storage }); const expected = [0, 2, 5]; for (const n of expected) { @@ -34,15 +32,14 @@ describe('fileChunkSink', () => { await writeAndClose(stream, 'a'); } - const actual = await fs.readDir(dir); + const actual = Object.keys(storage.files); expect(actual.sort(sortString)).toEqual(expected.map((e) => e.toString()).sort(sortString)); }); it('should remove previous files if count exceeds maxFiles', async () => { - const fs = mockStreamFileSystem(); - const dir = 'test'; + const storage = mockStreamFileSystem(); const maxFiles = 3; - const sink = new FileChunkSink({ file: (n) => path.join(dir, n.toString()), maxFiles, fs }); + const sink = new FileChunkSink({ file: (n) => n.toString(), maxFiles, storage }); const files = [0, 2, 5, 6, 79, 81, 38, -1, 3]; const expected = files.slice(-maxFiles); @@ -51,7 +48,7 @@ describe('fileChunkSink', () => { await writeAndClose(stream, 'a'); } - const actual = await fs.readDir(dir); + const actual = Object.keys(storage.files); expect(actual.sort(sortString)).toEqual(expected.map((e) => e.toString()).sort(sortString)); }); });