From 5012e66598ac10ce7dff7fc27bf4f67350eaadb4 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Thu, 16 Feb 2023 18:15:47 +1300 Subject: [PATCH 1/2] count decryption failures iin poll relations --- src/models/poll.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/models/poll.ts b/src/models/poll.ts index 8a080ef9015..3c50d3eab5b 100644 --- a/src/models/poll.ts +++ b/src/models/poll.ts @@ -28,6 +28,7 @@ export enum PollEvent { Update = "Poll.update", Responses = "Poll.Responses", Destroy = "Poll.Destroy", + UndecryptableRelations = "Poll.UndecryptableRelations" } export type PollEventHandlerMap = { @@ -35,6 +36,7 @@ export type PollEventHandlerMap = { [PollEvent.Destroy]: (pollIdentifier: string) => void; [PollEvent.End]: () => void; [PollEvent.Responses]: (responses: Relations) => void; + [PollEvent.UndecryptableRelations]: (count: number) => void; }; const filterResponseRelations = ( @@ -45,7 +47,6 @@ const filterResponseRelations = ( } => { const responseEvents = relationEvents.filter((event) => { if (event.isDecryptionFailure()) { - // @TODO(kerrya) PSG-1023 track and return these return; } return ( @@ -66,6 +67,11 @@ export class Poll extends TypedEventEmitter, P private relationsNextBatch: string | undefined; private responses: null | Relations = null; private endEvent: MatrixEvent | undefined; + /** + * Keep track of undecryptable relations + * As incomplete result sets affect poll results + */ + private undecryptableRelationEventIds = new Set(); public constructor(public readonly rootEvent: MatrixEvent, private matrixClient: MatrixClient, private room: Room) { super(); @@ -80,6 +86,10 @@ export class Poll extends TypedEventEmitter, P return this.rootEvent.getId()!; } + public get endEventId(): string | undefined { + return this.endEvent?.getId(); + } + public get isEnded(): boolean { return !!this.endEvent; } @@ -88,6 +98,10 @@ export class Poll extends TypedEventEmitter, P return this._isFetchingResponses; } + public get undecryptableRelationsCount(): number { + return this.undecryptableRelationEventIds.size; + } + public async getResponses(): Promise { // if we have already fetched some responses // just return them @@ -124,12 +138,17 @@ export class Poll extends TypedEventEmitter, P const pollEndTimestamp = this.endEvent?.getTs() || Number.MAX_SAFE_INTEGER; const { responseEvents } = filterResponseRelations([event], pollEndTimestamp); + this.countUndecryptableEvents([event]); + if (responseEvents.length) { responseEvents.forEach((event) => { this.responses!.addEvent(event); }); + + this.emit(PollEvent.Responses, this.responses); } + } private async fetchResponses(): Promise { @@ -173,6 +192,7 @@ export class Poll extends TypedEventEmitter, P this.relationsNextBatch = allRelations.nextBatch ?? undefined; this.responses = responses; + this.countUndecryptableEvents(allRelations.events); // while there are more pages of relations // fetch them @@ -209,6 +229,17 @@ export class Poll extends TypedEventEmitter, P this.emit(PollEvent.Responses, this.responses); } + private countUndecryptableEvents = (events: MatrixEvent[]) => { + const undecryptableEventIds = events.filter(event => event.isDecryptionFailure()).map(event => event.getId()!); + + const previousCount = this.undecryptableRelationsCount; + this.undecryptableRelationEventIds = new Set([...this.undecryptableRelationEventIds, ...undecryptableEventIds]); + + if (this.undecryptableRelationsCount !== previousCount) { + this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount); + } + } + private validateEndEvent(endEvent?: MatrixEvent): boolean { if (!endEvent) { return false; From 302a1f1486827dc9246b3075790e696400d713e5 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 17 Feb 2023 11:38:02 +1300 Subject: [PATCH 2/2] test undecryptable events in poll relations --- spec/unit/models/poll.spec.ts | 43 ++++++++++++++++++++++++++++++++++- src/models/poll.ts | 12 +++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/spec/unit/models/poll.spec.ts b/spec/unit/models/poll.spec.ts index 245d5646501..2acd405fbee 100644 --- a/spec/unit/models/poll.spec.ts +++ b/spec/unit/models/poll.spec.ts @@ -132,7 +132,7 @@ describe("Poll", () => { }); it("filters relations for relevent response events", async () => { - const replyEvent = new MatrixEvent({ type: "m.room.message" }); + const replyEvent = makeRelatedEvent({ type: "m.room.message" }); const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! }); const unstableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable }); @@ -188,6 +188,47 @@ describe("Poll", () => { }); }); + describe("undecryptable relations", () => { + it("counts undecryptable relation events when getting responses", async () => { + const replyEvent = makeRelatedEvent({ type: "m.room.message" }); + const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! }); + const undecryptableEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable }); + jest.spyOn(undecryptableEvent, "isDecryptionFailure").mockReturnValue(true); + + mockClient.relations.mockResolvedValue({ + events: [replyEvent, stableResponseEvent, undecryptableEvent], + }); + const poll = new Poll(basePollStartEvent, mockClient, room); + jest.spyOn(poll, "emit"); + await poll.getResponses(); + expect(poll.undecryptableRelationsCount).toBe(1); + expect(poll.emit).toHaveBeenCalledWith(PollEvent.UndecryptableRelations, 1); + }); + + it("adds to undercryptable event count when new relation is undecryptable", async () => { + const replyEvent = makeRelatedEvent({ type: "m.room.message" }); + const stableResponseEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.stable! }); + const undecryptableEvent = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable }); + const undecryptableEvent2 = makeRelatedEvent({ type: M_POLL_RESPONSE.unstable }); + jest.spyOn(undecryptableEvent, "isDecryptionFailure").mockReturnValue(true); + jest.spyOn(undecryptableEvent2, "isDecryptionFailure").mockReturnValue(true); + + mockClient.relations.mockResolvedValue({ + events: [replyEvent, stableResponseEvent, undecryptableEvent], + }); + const poll = new Poll(basePollStartEvent, mockClient, room); + jest.spyOn(poll, "emit"); + await poll.getResponses(); + expect(poll.undecryptableRelationsCount).toBe(1); + + await poll.onNewRelation(undecryptableEvent2); + + expect(poll.undecryptableRelationsCount).toBe(2); + + expect(poll.emit).toHaveBeenCalledWith(PollEvent.UndecryptableRelations, 2); + }); + }); + describe("with poll end event", () => { const stablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.stable!, sender: "@bob@server.org" }); const unstablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.unstable!, sender: "@bob@server.org" }); diff --git a/src/models/poll.ts b/src/models/poll.ts index 3c50d3eab5b..1d4344a901c 100644 --- a/src/models/poll.ts +++ b/src/models/poll.ts @@ -28,7 +28,7 @@ export enum PollEvent { Update = "Poll.update", Responses = "Poll.Responses", Destroy = "Poll.Destroy", - UndecryptableRelations = "Poll.UndecryptableRelations" + UndecryptableRelations = "Poll.UndecryptableRelations", } export type PollEventHandlerMap = { @@ -145,10 +145,8 @@ export class Poll extends TypedEventEmitter, P this.responses!.addEvent(event); }); - this.emit(PollEvent.Responses, this.responses); } - } private async fetchResponses(): Promise { @@ -229,8 +227,10 @@ export class Poll extends TypedEventEmitter, P this.emit(PollEvent.Responses, this.responses); } - private countUndecryptableEvents = (events: MatrixEvent[]) => { - const undecryptableEventIds = events.filter(event => event.isDecryptionFailure()).map(event => event.getId()!); + private countUndecryptableEvents = (events: MatrixEvent[]): void => { + const undecryptableEventIds = events + .filter((event) => event.isDecryptionFailure()) + .map((event) => event.getId()!); const previousCount = this.undecryptableRelationsCount; this.undecryptableRelationEventIds = new Set([...this.undecryptableRelationEventIds, ...undecryptableEventIds]); @@ -238,7 +238,7 @@ export class Poll extends TypedEventEmitter, P if (this.undecryptableRelationsCount !== previousCount) { this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount); } - } + }; private validateEndEvent(endEvent?: MatrixEvent): boolean { if (!endEvent) {