-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Adding a new controller for Metametrics Data Deletion (#24503)
- Loading branch information
1 parent
67cf369
commit d8b99ea
Showing
24 changed files
with
2,047 additions
and
58 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
181 changes: 181 additions & 0 deletions
181
app/scripts/controllers/metametrics-data-deletion/metametrics-data-deletion.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,181 @@ | ||
import { ControllerMessenger } from '@metamask/base-controller'; | ||
import { | ||
MetaMetricsDataDeletionController, | ||
type MetaMetricsDataDeletionControllerMessengerActions, | ||
} from './metametrics-data-deletion'; | ||
|
||
describe('MetaMetricsDataDeletionController', () => { | ||
describe('createMetaMetricsDataDeletionTask', () => { | ||
it('creates a data deletion task and stores ID when user is participating in metrics tracking', async () => { | ||
const mockMetaMetricsId = 'mockId'; | ||
const mockTaskId = 'mockTaskId'; | ||
const { controller, dataDeletionService } = setupController({ | ||
options: { | ||
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId), | ||
dataDeletionService: { | ||
createDataDeletionRegulationTask: jest | ||
.fn() | ||
.mockResolvedValue(mockTaskId), | ||
fetchDeletionRegulationStatus: jest | ||
.fn() | ||
.mockResolvedValue('UNKNOWN'), | ||
}, | ||
}, | ||
}); | ||
|
||
await controller.createMetaMetricsDataDeletionTask(); | ||
expect( | ||
dataDeletionService.createDataDeletionRegulationTask, | ||
).toHaveBeenCalledWith(mockMetaMetricsId); | ||
expect( | ||
dataDeletionService.createDataDeletionRegulationTask, | ||
).toHaveBeenCalledTimes(1); | ||
expect( | ||
dataDeletionService.fetchDeletionRegulationStatus, | ||
).toHaveBeenCalledTimes(1); | ||
expect(controller.state).toStrictEqual({ | ||
metaMetricsDataDeletionId: mockTaskId, | ||
metaMetricsDataDeletionTimestamp: expect.any(Number), | ||
metaMetricsDataDeletionStatus: 'UNKNOWN', | ||
}); | ||
}); | ||
it('creates a data deletion task and stores ID when user is not currently participating in metrics tracking', async () => { | ||
const mockMetaMetricsId = 'mockId'; | ||
const mockTaskId = 'mockTaskId'; | ||
const { controller, dataDeletionService } = setupController({ | ||
options: { | ||
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId), | ||
dataDeletionService: { | ||
createDataDeletionRegulationTask: jest | ||
.fn() | ||
.mockResolvedValue(mockTaskId), | ||
fetchDeletionRegulationStatus: jest | ||
.fn() | ||
.mockResolvedValue('UNKNOWN'), | ||
}, | ||
}, | ||
}); | ||
|
||
await controller.createMetaMetricsDataDeletionTask(); | ||
|
||
expect( | ||
dataDeletionService.createDataDeletionRegulationTask, | ||
).toHaveBeenCalledTimes(1); | ||
expect( | ||
dataDeletionService.fetchDeletionRegulationStatus, | ||
).toHaveBeenCalledTimes(1); | ||
expect( | ||
dataDeletionService.createDataDeletionRegulationTask, | ||
).toHaveBeenCalledWith(mockMetaMetricsId); | ||
expect(controller.state).toStrictEqual({ | ||
metaMetricsDataDeletionId: mockTaskId, | ||
metaMetricsDataDeletionTimestamp: expect.any(Number), | ||
metaMetricsDataDeletionStatus: 'UNKNOWN', | ||
}); | ||
}); | ||
|
||
it('fails to creates a data deletion task when user has never participating in metrics tracking', async () => { | ||
const { controller } = setupController({ | ||
options: { | ||
getMetaMetricsId: jest.fn().mockReturnValue(null), | ||
}, | ||
}); | ||
await expect( | ||
controller.createMetaMetricsDataDeletionTask(), | ||
).rejects.toThrow(); | ||
expect(controller.state).toStrictEqual({ | ||
metaMetricsDataDeletionId: null, | ||
metaMetricsDataDeletionTimestamp: expect.any(Number), | ||
}); | ||
}); | ||
}); | ||
describe('updateDataDeletionTaskStatus', () => { | ||
it('fetches and stores status of the delete regulation using delete regulation ID', async () => { | ||
const mockMetaMetricsId = 'mockId'; | ||
const mockTaskId = 'mockTaskId'; | ||
const { controller, dataDeletionService } = setupController({ | ||
options: { | ||
getMetaMetricsId: jest.fn().mockReturnValue(mockMetaMetricsId), | ||
dataDeletionService: { | ||
createDataDeletionRegulationTask: jest | ||
.fn() | ||
.mockResolvedValue(mockTaskId), | ||
fetchDeletionRegulationStatus: jest | ||
.fn() | ||
.mockResolvedValue('UNKNOWN'), | ||
}, | ||
}, | ||
}); | ||
await controller.createMetaMetricsDataDeletionTask(); | ||
await controller.updateDataDeletionTaskStatus(); | ||
expect( | ||
dataDeletionService.fetchDeletionRegulationStatus, | ||
).toHaveBeenCalledTimes(2); | ||
expect( | ||
dataDeletionService.fetchDeletionRegulationStatus, | ||
).toHaveBeenCalledWith(mockTaskId); | ||
expect(controller.state).toStrictEqual({ | ||
metaMetricsDataDeletionId: mockTaskId, | ||
metaMetricsDataDeletionTimestamp: expect.any(Number), | ||
metaMetricsDataDeletionStatus: 'UNKNOWN', | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
/** | ||
* Setup a test controller instance. | ||
* | ||
* @param options - Setup options. | ||
* @param options.options - Controller constructor options. | ||
* @returns The test controller, a messenger instance, and related mocks. | ||
*/ | ||
function setupController({ | ||
options, | ||
}: { | ||
options?: Partial< | ||
ConstructorParameters<typeof MetaMetricsDataDeletionController>[0] | ||
>; | ||
} = {}): { | ||
controller: MetaMetricsDataDeletionController; | ||
dataDeletionService: ConstructorParameters< | ||
typeof MetaMetricsDataDeletionController | ||
>[0]['dataDeletionService']; | ||
messenger: ControllerMessenger< | ||
MetaMetricsDataDeletionControllerMessengerActions, | ||
never | ||
>; | ||
} { | ||
const messenger = new ControllerMessenger< | ||
MetaMetricsDataDeletionControllerMessengerActions, | ||
never | ||
>(); | ||
const mockCreateDataDeletionRegulationTaskResponse = 'mockRegulateId'; | ||
const mockFetchDeletionRegulationStatusResponse = 'UNKNOWN'; | ||
const mockDataDeletionService = { | ||
createDataDeletionRegulationTask: jest | ||
.fn() | ||
.mockResolvedValue(mockCreateDataDeletionRegulationTaskResponse), | ||
fetchDeletionRegulationStatus: jest | ||
.fn() | ||
.mockResolvedValue(mockFetchDeletionRegulationStatusResponse), | ||
...options?.dataDeletionService, | ||
}; | ||
const constructorOptions = { | ||
dataDeletionService: mockDataDeletionService, | ||
getMetaMetricsId: jest.fn().mockReturnValue('mockMetaMetricsId'), | ||
messenger: messenger.getRestricted({ | ||
name: 'MetaMetricsDataDeletionController', | ||
allowedActions: [], | ||
allowedEvents: [], | ||
}), | ||
...options, | ||
}; | ||
const controller = new MetaMetricsDataDeletionController(constructorOptions); | ||
|
||
return { | ||
controller, | ||
dataDeletionService: constructorOptions.dataDeletionService, | ||
messenger, | ||
}; | ||
} |
183 changes: 183 additions & 0 deletions
183
app/scripts/controllers/metametrics-data-deletion/metametrics-data-deletion.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,183 @@ | ||
import { | ||
BaseController, | ||
RestrictedControllerMessenger, | ||
} from '@metamask/base-controller'; | ||
import { PublicInterface } from '@metamask/utils'; | ||
import type { DataDeletionService } from '../../services/data-deletion-service'; | ||
import { DeleteRegulationStatus } from '../../../../shared/constants/metametrics'; | ||
|
||
// Unique name for the controller | ||
const controllerName = 'MetaMetricsDataDeletionController'; | ||
|
||
/** | ||
* Timestamp at which regulation response is returned. | ||
*/ | ||
export type DataDeleteTimestamp = number; | ||
/** | ||
* Regulation Id retuned while creating a delete regulation. | ||
*/ | ||
export type DataDeleteRegulationId = string | null; | ||
|
||
/** | ||
* MetaMetricsDataDeletionController controller state | ||
* metaMetricsDataDeletionId - Regulation Id retuned while creating a delete regulation. | ||
* metaMetricsDataDeletionTimestamp - Timestamp at which the most recent regulation is created/requested for. | ||
* metaMetricsDataDeletionStatus - Status of the current delete regulation. | ||
*/ | ||
export type MetaMetricsDataDeletionState = { | ||
metaMetricsDataDeletionId: DataDeleteRegulationId; | ||
metaMetricsDataDeletionTimestamp: DataDeleteTimestamp; | ||
metaMetricsDataDeletionStatus?: DeleteRegulationStatus; | ||
}; | ||
|
||
const getDefaultState = (): MetaMetricsDataDeletionState => { | ||
return { | ||
metaMetricsDataDeletionId: null, | ||
metaMetricsDataDeletionTimestamp: 0, | ||
}; | ||
}; | ||
|
||
// Metadata for the controller state | ||
const metadata = { | ||
metaMetricsDataDeletionId: { | ||
persist: true, | ||
anonymous: true, | ||
}, | ||
metaMetricsDataDeletionTimestamp: { | ||
persist: true, | ||
anonymous: true, | ||
}, | ||
metaMetricsDataDeletionStatus: { | ||
persist: true, | ||
anonymous: true, | ||
}, | ||
}; | ||
|
||
// Describes the action creating the delete regulation task | ||
export type CreateMetaMetricsDataDeletionTaskAction = { | ||
type: `${typeof controllerName}:createMetaMetricsDataDeletionTask`; | ||
handler: MetaMetricsDataDeletionController['createMetaMetricsDataDeletionTask']; | ||
}; | ||
|
||
// Describes the action to check the existing regulation status | ||
export type UpdateDataDeletionTaskStatusAction = { | ||
type: `${typeof controllerName}:updateDataDeletionTaskStatus`; | ||
handler: MetaMetricsDataDeletionController['updateDataDeletionTaskStatus']; | ||
}; | ||
|
||
// Union of all possible actions for the messenger | ||
export type MetaMetricsDataDeletionControllerMessengerActions = | ||
| CreateMetaMetricsDataDeletionTaskAction | ||
| UpdateDataDeletionTaskStatusAction; | ||
|
||
// Type for the messenger of MetaMetricsDataDeletionController | ||
export type MetaMetricsDataDeletionControllerMessenger = | ||
RestrictedControllerMessenger< | ||
typeof controllerName, | ||
MetaMetricsDataDeletionControllerMessengerActions, | ||
never, | ||
never, | ||
never | ||
>; | ||
|
||
/** | ||
* Controller responsible for maintaining | ||
* state related to Metametrics data deletion | ||
*/ | ||
export class MetaMetricsDataDeletionController extends BaseController< | ||
typeof controllerName, | ||
MetaMetricsDataDeletionState, | ||
MetaMetricsDataDeletionControllerMessenger | ||
> { | ||
#dataDeletionService: PublicInterface<DataDeletionService>; | ||
|
||
#getMetaMetricsId: () => string | null; | ||
|
||
/** | ||
* Creates a MetaMetricsDataDeletionController instance. | ||
* | ||
* @param args - The arguments to this function. | ||
* @param args.dataDeletionService - The service used for deleting data. | ||
* @param args.messenger - Messenger used to communicate with BaseV2 controller. | ||
* @param args.state - Initial state to set on this controller. | ||
* @param args.getMetaMetricsId - A function that returns the current MetaMetrics ID. | ||
*/ | ||
constructor({ | ||
dataDeletionService, | ||
messenger, | ||
state, | ||
getMetaMetricsId, | ||
}: { | ||
dataDeletionService: PublicInterface<DataDeletionService>; | ||
messenger: MetaMetricsDataDeletionControllerMessenger; | ||
state?: Partial<MetaMetricsDataDeletionState>; | ||
getMetaMetricsId: () => string | null; | ||
}) { | ||
// Call the constructor of BaseControllerV2 | ||
super({ | ||
messenger, | ||
metadata, | ||
name: controllerName, | ||
state: { ...getDefaultState(), ...state }, | ||
}); | ||
this.#getMetaMetricsId = getMetaMetricsId; | ||
this.#dataDeletionService = dataDeletionService; | ||
this.#registerMessageHandlers(); | ||
} | ||
|
||
/** | ||
* Constructor helper for registering this controller's messaging system | ||
* actions. | ||
*/ | ||
#registerMessageHandlers(): void { | ||
this.messagingSystem.registerActionHandler( | ||
`${controllerName}:createMetaMetricsDataDeletionTask`, | ||
this.createMetaMetricsDataDeletionTask.bind(this), | ||
); | ||
|
||
this.messagingSystem.registerActionHandler( | ||
`${controllerName}:updateDataDeletionTaskStatus`, | ||
this.updateDataDeletionTaskStatus.bind(this), | ||
); | ||
} | ||
|
||
/** | ||
* Creating the delete regulation using source regulation | ||
* | ||
*/ | ||
async createMetaMetricsDataDeletionTask(): Promise<void> { | ||
const metaMetricsId = this.#getMetaMetricsId(); | ||
if (!metaMetricsId) { | ||
throw new Error('MetaMetrics ID not found'); | ||
} | ||
|
||
const deleteRegulateId = | ||
await this.#dataDeletionService.createDataDeletionRegulationTask( | ||
metaMetricsId, | ||
); | ||
this.update((state) => { | ||
state.metaMetricsDataDeletionId = deleteRegulateId ?? null; | ||
state.metaMetricsDataDeletionTimestamp = Date.now(); | ||
}); | ||
await this.updateDataDeletionTaskStatus(); | ||
} | ||
|
||
/** | ||
* To check the status of the current delete regulation. | ||
*/ | ||
async updateDataDeletionTaskStatus(): Promise<void> { | ||
const deleteRegulationId = this.state.metaMetricsDataDeletionId; | ||
if (!deleteRegulationId) { | ||
throw new Error('Delete Regulation id not found'); | ||
} | ||
|
||
const deletionStatus = | ||
await this.#dataDeletionService.fetchDeletionRegulationStatus( | ||
deleteRegulationId, | ||
); | ||
|
||
this.update((state) => { | ||
state.metaMetricsDataDeletionStatus = deletionStatus ?? undefined; | ||
}); | ||
} | ||
} |
Oops, something went wrong.