-
Notifications
You must be signed in to change notification settings - Fork 135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Identity Metadata Repository setup #2520
Changes from 1 commit
46c7f0b
302377b
1718335
0a230a0
270c219
84c55ec
982b4a3
a646146
a1930e1
9b28f4b
81ef6c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -250,6 +250,7 @@ export const renderManage = async ({ | |
for (;;) { | ||
let anchorInfo: IdentityAnchorInfo; | ||
try { | ||
// TODO: commit state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be done in another PR, but I want to remember exactly where to put it. |
||
anchorInfo = await withLoader(() => connection.getAnchorInfo()); | ||
} catch (error: unknown) { | ||
await displayFailedToListDevices( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
import { MetadataMapV2 } from "$generated/internet_identity_types"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to have a test to ensure that the repository leaves additional data unchanged in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a test for this: I rephrased the description to use "additional", maybe that was the confusing part. |
||
import { | ||
AnchorMetadata, | ||
AnchorMetadataRepository, | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
} from "./anchorMetadata"; | ||
|
||
const recoveryPageShownTimestampMillis = 1234567890; | ||
const mockRawMetadata: MetadataMapV2 = [ | ||
[ | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
{ String: String(recoveryPageShownTimestampMillis) }, | ||
], | ||
]; | ||
const mockAnchorMetadata: AnchorMetadata = { | ||
recoveryPageShownTimestampMillis, | ||
}; | ||
|
||
// Used to await that the getter has resolved. | ||
let getterResponse: MetadataMapV2 | null | undefined = null; | ||
const getterMockSuccess = vi.fn().mockImplementation(async () => { | ||
// The `await` is necessary to make sure that the `getterResponse` is set before the test continues. | ||
getterResponse = await mockRawMetadata; | ||
return mockRawMetadata; | ||
}); | ||
const getterMockError = vi.fn().mockImplementation(async () => { | ||
// The `await` is necessary to make sure that the `getterResponse` is set before the test continues. | ||
getterResponse = await null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm missing some context here. Why does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For some reason it wasn't waiting before. But I tried again and it was working now. Removed. |
||
throw new Error("test error"); | ||
}); | ||
const setterMockSuccess = vi.fn(); | ||
const setterMockError = vi.fn().mockRejectedValue("test error"); | ||
|
||
beforeEach(() => { | ||
getterResponse = undefined; | ||
vi.clearAllMocks(); | ||
vi.spyOn(console, "warn").mockImplementation(() => {}); | ||
}); | ||
|
||
test("AnchorMetadataRepository loads data on init in the background", async () => { | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockSuccess, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockRawMetadata)); | ||
|
||
expect(getterMockSuccess).toHaveBeenCalledTimes(1); | ||
expect(await instance.getMetadata()).toEqual(mockAnchorMetadata); | ||
}); | ||
|
||
test("AnchorMetadataRepository loads data again on init in the background", async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment below, but I think the metadata should not be loaded again. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
const getterMockErrorOnce = vi | ||
.fn() | ||
.mockImplementationOnce(async () => { | ||
// The `await` is necessary to make sure that the `getterResponse` is set before the test continues. | ||
getterResponse = await null; | ||
throw new Error("test error"); | ||
}) | ||
.mockResolvedValue(mockRawMetadata); | ||
|
||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockErrorOnce, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
// Wait for the first getter to fail. | ||
await vi.waitFor(() => expect(getterResponse).toEqual(null)); | ||
|
||
// Error is not thrown, but a warning is logged. | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(getterMockErrorOnce).toHaveBeenCalledTimes(1); | ||
|
||
expect(await instance.getMetadata()).toEqual(mockAnchorMetadata); | ||
expect(getterMockErrorOnce).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
test("AnchorMetadataRepository returns no metadata if fetching fails without raising an error", async () => { | ||
lmuntaner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockError, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
// Wait for the first getter to fail. | ||
await vi.waitFor(() => expect(getterResponse).toEqual(null)); | ||
|
||
// Error is not thrown, but warnings is logged. | ||
expect(console.warn).toHaveBeenCalledTimes(1); | ||
expect(getterMockError).toHaveBeenCalledTimes(1); | ||
|
||
expect(await instance.getMetadata()).toEqual(undefined); | ||
expect(getterMockError).toHaveBeenCalledTimes(2); | ||
expect(console.warn).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
test("AnchorMetadataRepository changes data in memory", async () => { | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockSuccess, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockRawMetadata)); | ||
|
||
const newRecoveryPageShownTimestampMillis = 9876543210; | ||
await instance.setPartialMetadata({ | ||
recoveryPageShownTimestampMillis: newRecoveryPageShownTimestampMillis, | ||
}); | ||
|
||
expect(await instance.getMetadata()).toEqual({ | ||
recoveryPageShownTimestampMillis: newRecoveryPageShownTimestampMillis, | ||
}); | ||
}); | ||
|
||
test("AnchorMetadataRepository commits updated metadata to canister", async () => { | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockSuccess, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockRawMetadata)); | ||
|
||
const newRecoveryPageShownTimestampMillis = 9876543210; | ||
await instance.setPartialMetadata({ | ||
recoveryPageShownTimestampMillis: newRecoveryPageShownTimestampMillis, | ||
}); | ||
|
||
expect(setterMockSuccess).not.toHaveBeenCalled(); | ||
await instance.commitMetadata(); | ||
|
||
expect(setterMockSuccess).toHaveBeenCalledTimes(1); | ||
expect(setterMockSuccess).toHaveBeenCalledWith([ | ||
[ | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
{ String: String(newRecoveryPageShownTimestampMillis) }, | ||
], | ||
]); | ||
}); | ||
|
||
test("AnchorMetadataRepository doesn't commit to canister without changes", async () => { | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockSuccess, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockRawMetadata)); | ||
|
||
expect(setterMockSuccess).not.toHaveBeenCalled(); | ||
await instance.commitMetadata(); | ||
|
||
expect(setterMockSuccess).not.toHaveBeenCalled(); | ||
}); | ||
|
||
test("AnchorMetadataRepository doesn't raise an error if committing fails", async () => { | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMockSuccess, | ||
setter: setterMockError, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockRawMetadata)); | ||
|
||
const newRecoveryPageShownTimestampMillis = 9876543210; | ||
const newMetadata = { | ||
recoveryPageShownTimestampMillis: newRecoveryPageShownTimestampMillis, | ||
}; | ||
await instance.setPartialMetadata(newMetadata); | ||
|
||
expect(setterMockError).not.toHaveBeenCalled(); | ||
const committed = await instance.commitMetadata(); | ||
|
||
expect(committed).toBe(false); | ||
expect(setterMockError).toHaveBeenCalledTimes(1); | ||
expect(setterMockError).toHaveBeenCalledWith([ | ||
[ | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
{ String: String(newRecoveryPageShownTimestampMillis) }, | ||
], | ||
]); | ||
|
||
// But the value in memory is not lost. | ||
expect(await instance.getMetadata()).toEqual(newMetadata); | ||
}); | ||
|
||
test("AnchorMetadataRepository commits all received metadata to canister after update", async () => { | ||
const anotherMetadataEntry: [string, { String: string }] = [ | ||
"otherKey", | ||
{ String: "otherValue" }, | ||
]; | ||
const mockMoreRawMetadata: MetadataMapV2 = [ | ||
[ | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
{ String: String(recoveryPageShownTimestampMillis) }, | ||
], | ||
anotherMetadataEntry, | ||
]; | ||
const getterMock = vi.fn().mockImplementation(async () => { | ||
// The `await` is necessary to make sure that the `getterResponse` is set before the test continues. | ||
getterResponse = await mockMoreRawMetadata; | ||
return mockMoreRawMetadata; | ||
}); | ||
const instance = AnchorMetadataRepository.init({ | ||
getter: getterMock, | ||
setter: setterMockSuccess, | ||
}); | ||
|
||
await vi.waitFor(() => expect(getterResponse).toEqual(mockMoreRawMetadata)); | ||
|
||
const newRecoveryPageShownTimestampMillis = 9876543210; | ||
await instance.setPartialMetadata({ | ||
recoveryPageShownTimestampMillis: newRecoveryPageShownTimestampMillis, | ||
}); | ||
|
||
expect(setterMockSuccess).not.toHaveBeenCalled(); | ||
await instance.commitMetadata(); | ||
|
||
expect(setterMockSuccess).toHaveBeenCalledTimes(1); | ||
expect(setterMockSuccess).toHaveBeenCalledWith([ | ||
[ | ||
RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS, | ||
{ String: String(newRecoveryPageShownTimestampMillis) }, | ||
], | ||
anotherMetadataEntry, | ||
]); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be done in another PR, but I want to remember exactly where to put it.