-
Notifications
You must be signed in to change notification settings - Fork 318
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(device): implement deleteAppData logic in live-common device
- Loading branch information
1 parent
3b9c1cf
commit d4af368
Showing
9 changed files
with
494 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
libs/ledger-live-common/src/device/use-cases/appDataBackup/deleteAppData.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { Observable, firstValueFrom, lastValueFrom } from "rxjs"; | ||
import { deleteAppData } from "./deleteAppData"; | ||
import { | ||
AppStorageType, | ||
DeleteAppDataError, | ||
DeleteAppDataEvent, | ||
DeleteAppDataEventType, | ||
StorageProvider, | ||
} from "./types"; | ||
import { DeviceModelId } from "@ledgerhq/devices"; | ||
|
||
jest.mock("@ledgerhq/hw-transport"); | ||
|
||
describe("deleteAppData", () => { | ||
let appName: string; | ||
let deviceModelId: DeviceModelId; | ||
let storageProvider: StorageProvider<AppStorageType>; | ||
|
||
const setItem = jest.fn(); | ||
const getItem = jest.fn(); | ||
const removeItem = jest.fn(); | ||
|
||
beforeEach(() => { | ||
appName = "MyApp"; | ||
deviceModelId = DeviceModelId.stax; | ||
storageProvider = { | ||
getItem, | ||
setItem, | ||
removeItem, | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe("success case", () => { | ||
it("should delete the app data by emitting relative events sequentially", async () => { | ||
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData( | ||
appName, | ||
deviceModelId, | ||
storageProvider, | ||
); | ||
const events: DeleteAppDataEvent[] = []; | ||
|
||
getItem.mockResolvedValue({ | ||
appDataInfo: { | ||
size: 6, | ||
dataVersion: "test", | ||
hasSettings: true, | ||
hasData: true, | ||
hash: "test", | ||
}, | ||
appData: "bGVkZ2Vy", // base64 encoded "ledger" | ||
}); | ||
|
||
removeItem.mockResolvedValue(undefined); | ||
|
||
// Subscribe to the observable to receive the delete events | ||
deleteObservable.subscribe((event: DeleteAppDataEvent) => { | ||
events.push(event); | ||
}); | ||
|
||
const firstValue: DeleteAppDataEvent = await firstValueFrom(deleteObservable); | ||
expect(firstValue).toEqual({ | ||
type: DeleteAppDataEventType.AppDataDeleteStarted, | ||
}); | ||
|
||
const lastValue: DeleteAppDataEvent = await lastValueFrom(deleteObservable); | ||
expect(lastValue).toEqual({ | ||
type: DeleteAppDataEventType.AppDataDeleted, | ||
}); | ||
|
||
expect(events).toHaveLength(2); | ||
}); | ||
|
||
it("should emit specific event when there is no app data to delete", async () => { | ||
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData( | ||
appName, | ||
deviceModelId, | ||
storageProvider, | ||
); | ||
const events: DeleteAppDataEvent[] = []; | ||
|
||
getItem.mockResolvedValue(null); | ||
|
||
// Subscribe to the observable to receive the delete events | ||
deleteObservable.subscribe((event: DeleteAppDataEvent) => { | ||
events.push(event); | ||
}); | ||
|
||
const firstValue: DeleteAppDataEvent = await firstValueFrom(deleteObservable); | ||
expect(firstValue).toEqual({ | ||
type: DeleteAppDataEventType.AppDataDeleteStarted, | ||
}); | ||
|
||
const lastValue: DeleteAppDataEvent = await lastValueFrom(deleteObservable); | ||
expect(lastValue).toEqual({ | ||
type: DeleteAppDataEventType.NoAppDataToDelete, | ||
}); | ||
|
||
expect(events).toHaveLength(2); | ||
}); | ||
}); | ||
|
||
describe("error case", () => { | ||
it("should emit an error event when there is an error during the deletion process", async () => { | ||
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData( | ||
appName, | ||
deviceModelId, | ||
storageProvider, | ||
); | ||
|
||
const events: DeleteAppDataEvent[] = []; | ||
|
||
getItem.mockResolvedValue({ | ||
appDataInfo: { | ||
size: 6, | ||
dataVersion: "test", | ||
hasSettings: true, | ||
hasData: true, | ||
hash: "test", | ||
}, | ||
appData: "bGVkZ2Vy", // base64 encoded "ledger" | ||
}); | ||
|
||
removeItem.mockRejectedValue(new Error("Failed to delete app data")); | ||
|
||
// Subscribe to the observable to receive the delete events | ||
deleteObservable.subscribe((event: DeleteAppDataEvent) => { | ||
events.push(event); | ||
}); | ||
|
||
const firstValue: DeleteAppDataEvent = await firstValueFrom(deleteObservable); | ||
expect(firstValue).toEqual({ | ||
type: DeleteAppDataEventType.AppDataDeleteStarted, | ||
}); | ||
|
||
lastValueFrom(deleteObservable).catch(e => { | ||
expect(e).toBeInstanceOf(DeleteAppDataError); | ||
expect(e.message).toBe("Failed to delete app data"); | ||
}); | ||
|
||
expect(events).toHaveLength(1); | ||
}); | ||
|
||
it("should emit an error event when there is an error getting the app data from storage", async () => { | ||
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData( | ||
appName, | ||
deviceModelId, | ||
storageProvider, | ||
); | ||
|
||
getItem.mockRejectedValue(new Error("Error fetching app data")); | ||
|
||
lastValueFrom(deleteObservable).catch(e => { | ||
expect(e).toBeInstanceOf(Error); | ||
expect(e.message).toBe("Error fetching app data"); | ||
}); | ||
}); | ||
|
||
it("should emit an error event when there is an unkown error getting the app data from storage", async () => { | ||
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData( | ||
appName, | ||
deviceModelId, | ||
storageProvider, | ||
); | ||
|
||
getItem.mockRejectedValue(null); | ||
|
||
lastValueFrom(deleteObservable).catch(e => { | ||
expect(e).toBeInstanceOf(Error); | ||
expect(e.message).toBe("Unknown error"); | ||
}); | ||
}); | ||
}); | ||
}); |
54 changes: 54 additions & 0 deletions
54
libs/ledger-live-common/src/device/use-cases/appDataBackup/deleteAppData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { catchError, from, Observable, of, switchMap, throwError } from "rxjs"; | ||
import { DeviceModelId } from "@ledgerhq/devices"; | ||
import { | ||
AppName, | ||
AppStorageType, | ||
DeleteAppDataError, | ||
DeleteAppDataEvent, | ||
DeleteAppDataEventType, | ||
StorageProvider, | ||
} from "./types"; | ||
|
||
export function deleteAppData( | ||
appName: AppName, | ||
deviceModelId: DeviceModelId, | ||
storageProvider: StorageProvider<AppStorageType>, | ||
): Observable<DeleteAppDataEvent> { | ||
const obs = new Observable<DeleteAppDataEvent>(subscriber => { | ||
subscriber.next({ type: DeleteAppDataEventType.AppDataDeleteStarted }); | ||
const sub = from(storageProvider.getItem(`${deviceModelId}-${appName}`)) | ||
.pipe( | ||
catchError(e => { | ||
if (e instanceof Error) { | ||
subscriber.error(e); | ||
} else { | ||
subscriber.error(new Error("Unknown error")); | ||
} | ||
return of(null); | ||
}), | ||
switchMap(async item => { | ||
if (item) { | ||
try { | ||
await storageProvider.removeItem(`${deviceModelId}-${appName}`); | ||
subscriber.next({ type: DeleteAppDataEventType.AppDataDeleted }); | ||
subscriber.complete(); | ||
} catch (e: unknown) { | ||
const message = e instanceof Error ? e.message : "Error deleting app data"; | ||
throwError(() => new DeleteAppDataError(message)); | ||
} | ||
} else { | ||
subscriber.next({ type: DeleteAppDataEventType.NoAppDataToDelete }); | ||
subscriber.complete(); | ||
} | ||
}), | ||
) | ||
.subscribe(); | ||
|
||
return () => { | ||
subscriber.complete(); | ||
sub.unsubscribe(); | ||
}; | ||
}); | ||
|
||
return obs; | ||
} |
Oops, something went wrong.