Skip to content

Commit

Permalink
Polls: count undecryptable poll relations (#3163)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerry authored Feb 20, 2023
1 parent 89df43a commit 1a91ba5
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 2 deletions.
43 changes: 42 additions & 1 deletion spec/unit/models/poll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });

Expand Down Expand Up @@ -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: "@[email protected]" });
const unstablePollEndEvent = makeRelatedEvent({ type: M_POLL_END.unstable!, sender: "@[email protected]" });
Expand Down
33 changes: 32 additions & 1 deletion src/models/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export enum PollEvent {
Update = "Poll.update",
Responses = "Poll.Responses",
Destroy = "Poll.Destroy",
UndecryptableRelations = "Poll.UndecryptableRelations",
}

export type PollEventHandlerMap = {
[PollEvent.Update]: (event: MatrixEvent, poll: Poll) => void;
[PollEvent.Destroy]: (pollIdentifier: string) => void;
[PollEvent.End]: () => void;
[PollEvent.Responses]: (responses: Relations) => void;
[PollEvent.UndecryptableRelations]: (count: number) => void;
};

const filterResponseRelations = (
Expand All @@ -45,7 +47,6 @@ const filterResponseRelations = (
} => {
const responseEvents = relationEvents.filter((event) => {
if (event.isDecryptionFailure()) {
// @TODO(kerrya) PSG-1023 track and return these
return;
}
return (
Expand All @@ -66,6 +67,11 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, 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<string>();

public constructor(public readonly rootEvent: MatrixEvent, private matrixClient: MatrixClient, private room: Room) {
super();
Expand All @@ -80,6 +86,10 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
return this.rootEvent.getId()!;
}

public get endEventId(): string | undefined {
return this.endEvent?.getId();
}

public get isEnded(): boolean {
return !!this.endEvent;
}
Expand All @@ -88,6 +98,10 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
return this._isFetchingResponses;
}

public get undecryptableRelationsCount(): number {
return this.undecryptableRelationEventIds.size;
}

public async getResponses(): Promise<Relations> {
// if we have already fetched some responses
// just return them
Expand Down Expand Up @@ -124,10 +138,13 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, 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);
}
}
Expand Down Expand Up @@ -173,6 +190,7 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P

this.relationsNextBatch = allRelations.nextBatch ?? undefined;
this.responses = responses;
this.countUndecryptableEvents(allRelations.events);

// while there are more pages of relations
// fetch them
Expand Down Expand Up @@ -209,6 +227,19 @@ export class Poll extends TypedEventEmitter<Exclude<PollEvent, PollEvent.New>, P
this.emit(PollEvent.Responses, this.responses);
}

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]);

if (this.undecryptableRelationsCount !== previousCount) {
this.emit(PollEvent.UndecryptableRelations, this.undecryptableRelationsCount);
}
};

private validateEndEvent(endEvent?: MatrixEvent): boolean {
if (!endEvent) {
return false;
Expand Down

0 comments on commit 1a91ba5

Please sign in to comment.