Skip to content

Commit

Permalink
feat(device): implement deleteAppData logic in live-common device
Browse files Browse the repository at this point in the history
  • Loading branch information
valpinkman committed Aug 29, 2024
1 parent d4af368 commit 1bcff16
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 196 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-mayflies-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/live-common": minor
---

Add new use case to delete locally stored app data (llm/lld)
9 changes: 9 additions & 0 deletions apps/ledger-live-desktop/static/i18n/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6222,6 +6222,15 @@
},
"TonMinimumRequired": {
"title": "Insufficient funds. The minimum required balance is 0.02 TON."
},
"BackupAppDataError": {
"title": "Error during app data backup"
},
"RestoreAppDataError": {
"title": "Error restoring app data"
},
"DeleteAppDataError": {
"title": "Error deleting app data"
}
},
"cryptoOrg": {
Expand Down
9 changes: 9 additions & 0 deletions apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,15 @@
},
"TonMinimumRequired": {
"title": "Insufficient funds. The minimum required balance is 0.02 TON."
},
"BackupAppDataError": {
"title": "Error during app data backup"
},
"RestoreAppDataError": {
"title": "Error restoring app data"
},
"DeleteAppDataError": {
"title": "Error deleting app data"
}
},
"crash": {
Expand Down
5 changes: 4 additions & 1 deletion libs/ledger-live-common/.unimportedrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,10 @@
"src/notifications/AnnouncementProvider/helpers.ts",
"src/reactNativeSvg.d.ts",
"src/rxjs/operators/throwIf.ts",
"src/walletSync/getEnvironmentParams.ts"
"src/walletSync/getEnvironmentParams.ts",
"src/device/use-cases/appDataBackup/deleteAppData.ts",
"src/device/use-cases/appDataBackup/deleteAppDataUseCase.ts",
"src/device/use-cases/appDataBackup/deleteAppDataUseCaseDI.ts"
],
"ignoreUnused": [
"@ledgerhq/hw-transport-mocker",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Observable, firstValueFrom, lastValueFrom } from "rxjs";
import { Observable } from "rxjs";
import { deleteAppData } from "./deleteAppData";
import {
AppStorageType,
Expand Down Expand Up @@ -35,7 +35,7 @@ describe("deleteAppData", () => {
});

describe("success case", () => {
it("should delete the app data by emitting relative events sequentially", async () => {
it("should delete the app data by emitting relative events sequentially", done => {
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData(
appName,
deviceModelId,
Expand All @@ -56,25 +56,25 @@ describe("deleteAppData", () => {

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,
deleteObservable.subscribe({
next: (event: DeleteAppDataEvent) => events.push(event),
complete: () => {
expect(events).toHaveLength(2);
expect(events[0]).toEqual({
type: DeleteAppDataEventType.AppDataDeleteStarted,
});
expect(events[1]).toEqual({
type: DeleteAppDataEventType.AppDataDeleted,
});
done();
},
error: (e: Error) => {
done(e);
},
});

expect(events).toHaveLength(2);
});

it("should emit specific event when there is no app data to delete", async () => {
it("should emit specific event when there is no app data to delete", done => {
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData(
appName,
deviceModelId,
Expand All @@ -84,27 +84,27 @@ describe("deleteAppData", () => {

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,
deleteObservable.subscribe({
next: (event: DeleteAppDataEvent) => events.push(event),
complete: () => {
expect(events).toHaveLength(2);
expect(events[0]).toEqual({
type: DeleteAppDataEventType.AppDataDeleteStarted,
});
expect(events[1]).toEqual({
type: DeleteAppDataEventType.NoAppDataToDelete,
});
done();
},
error: (e: Error) => {
done(e);
},
});

expect(events).toHaveLength(2);
});
});

describe("error case", () => {
it("should emit an error event when there is an error during the deletion process", async () => {
it("should emit an error event when there is an error during the deletion process", done => {
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData(
appName,
deviceModelId,
Expand All @@ -127,24 +127,28 @@ describe("deleteAppData", () => {
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");
deleteObservable.subscribe({
next: (event: DeleteAppDataEvent) => events.push(event),
complete: () => {
done(new Error("Complete should not be called"));
},
error: (e: Error) => {
try {
expect(events).toHaveLength(1);
expect(events[0]).toEqual({
type: DeleteAppDataEventType.AppDataDeleteStarted,
});
expect(e).toBeInstanceOf(DeleteAppDataError);
expect(e.message).toBe("Failed to delete app data");
done();
} catch (error) {
done(error);
}
},
});

expect(events).toHaveLength(1);
});

it("should emit an error event when there is an error getting the app data from storage", async () => {
it("should emit an error event when there is an error getting the app data from storage", done => {
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData(
appName,
deviceModelId,
Expand All @@ -153,13 +157,29 @@ describe("deleteAppData", () => {

getItem.mockRejectedValue(new Error("Error fetching app data"));

lastValueFrom(deleteObservable).catch(e => {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe("Error fetching app data");
const events: DeleteAppDataEvent[] = [];

deleteObservable.subscribe({
next: (event: DeleteAppDataEvent) => events.push(event),
complete: () => {
done(new Error("Complete should not be called"));
},
error: (e: Error) => {
try {
expect(events).toHaveLength(1);
expect(events[0]).toEqual({
type: DeleteAppDataEventType.AppDataDeleteStarted,
});
expect(e).toEqual(new Error("Error fetching app data"));
done();
} catch (error) {
done(error);
}
},
});
});

it("should emit an error event when there is an unkown error getting the app data from storage", async () => {
it("should emit an error event when there is an unkown error getting the app data from storage", done => {
const deleteObservable: Observable<DeleteAppDataEvent> = deleteAppData(
appName,
deviceModelId,
Expand All @@ -168,9 +188,21 @@ describe("deleteAppData", () => {

getItem.mockRejectedValue(null);

lastValueFrom(deleteObservable).catch(e => {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe("Unknown error");
const events: DeleteAppDataEvent[] = [];

deleteObservable.subscribe({
next: (event: DeleteAppDataEvent) => events.push(event),
complete: () => {
done(new Error("Complete should not be called"));
},
error: (e: Error) => {
try {
expect(e).toEqual(new Error("Unknown error"));
done();
} catch (error) {
done(error);
}
},
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { catchError, from, Observable, of, switchMap, throwError } from "rxjs";
import { catchError, from, Observable, of, switchMap } from "rxjs";
import { DeviceModelId } from "@ledgerhq/devices";
import {
AppName,
Expand All @@ -18,14 +18,6 @@ export function deleteAppData(
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 {
Expand All @@ -34,13 +26,21 @@ export function deleteAppData(
subscriber.complete();
} catch (e: unknown) {
const message = e instanceof Error ? e.message : "Error deleting app data";
throwError(() => new DeleteAppDataError(message));
throw new DeleteAppDataError(message);
}
} else {
subscriber.next({ type: DeleteAppDataEventType.NoAppDataToDelete });
subscriber.complete();
}
}),
catchError(e => {
if (e instanceof Error || e instanceof DeleteAppDataError) {
subscriber.error(e);
} else {
subscriber.error(new Error("Unknown error"));
}
return of(null);
}),
)
.subscribe();

Expand Down
Loading

0 comments on commit 1bcff16

Please sign in to comment.