From cf02c35b49c3168d3f82bff78dde9dcbc3167d73 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Wed, 21 Jun 2023 13:11:30 +0200 Subject: [PATCH] fix: save created revision on realtime note destroy Signed-off-by: Tilman Vatteroth --- .../realtime-note.service.spec.ts | 6 +-- .../realtime-note/realtime-note.service.ts | 2 +- .../src/revisions/revisions.service.spec.ts | 46 +++++++++++++++++++ backend/src/revisions/revisions.service.ts | 34 ++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/backend/src/realtime/realtime-note/realtime-note.service.spec.ts b/backend/src/realtime/realtime-note/realtime-note.service.spec.ts index 35f60e3d4..8232b4e11 100644 --- a/backend/src/realtime/realtime-note/realtime-note.service.spec.ts +++ b/backend/src/realtime/realtime-note/realtime-note.service.spec.ts @@ -78,7 +78,7 @@ describe('RealtimeNoteService', () => { revisionsService = Mock.of({ getLatestRevision: jest.fn(), - createRevision: jest.fn(), + createAndSaveRevision: jest.fn(), }); consoleLoggerService = Mock.of({ @@ -294,8 +294,8 @@ describe('RealtimeNoteService', () => { await realtimeNoteService.getOrCreateRealtimeNote(note); const createRevisionSpy = jest - .spyOn(revisionsService, 'createRevision') - .mockImplementation(() => Promise.resolve(Mock.of())); + .spyOn(revisionsService, 'createAndSaveRevision') + .mockResolvedValue(); realtimeNote.emit('beforeDestroy'); expect(createRevisionSpy).toHaveBeenCalledWith( diff --git a/backend/src/realtime/realtime-note/realtime-note.service.ts b/backend/src/realtime/realtime-note/realtime-note.service.ts index d7900dd3b..19ea69b8f 100644 --- a/backend/src/realtime/realtime-note/realtime-note.service.ts +++ b/backend/src/realtime/realtime-note/realtime-note.service.ts @@ -44,7 +44,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown { */ public saveRealtimeNote(realtimeNote: RealtimeNote): void { this.revisionsService - .createRevision( + .createAndSaveRevision( realtimeNote.getNote(), realtimeNote.getRealtimeDoc().getCurrentContent(), realtimeNote.getRealtimeDoc().encodeStateAsUpdate(), diff --git a/backend/src/revisions/revisions.service.spec.ts b/backend/src/revisions/revisions.service.spec.ts index da0183adf..8c4dd3e2c 100644 --- a/backend/src/revisions/revisions.service.spec.ts +++ b/backend/src/revisions/revisions.service.spec.ts @@ -364,4 +364,50 @@ describe('RevisionsService', () => { expect(saveSpy).not.toHaveBeenCalled(); }); }); + + describe('createAndSaveRevision', () => { + it('creates and saves a new revision', async () => { + const newRevision = Mock.of(); + const createRevisionSpy = jest + .spyOn(service, 'createRevision') + .mockResolvedValue(newRevision); + const repoSaveSpy = jest + .spyOn(revisionRepo, 'save') + .mockResolvedValue(newRevision); + + const note = Mock.of({}); + const newContent = 'MockContent'; + + const yjsState = [0, 1, 2, 3, 4, 5]; + + await service.createAndSaveRevision(note, newContent, yjsState); + expect(createRevisionSpy).toHaveBeenCalledWith( + note, + newContent, + yjsState, + ); + expect(repoSaveSpy).toHaveBeenCalledWith(newRevision); + }); + + it("doesn't save if no revision has been created", async () => { + const createRevisionSpy = jest + .spyOn(service, 'createRevision') + .mockResolvedValue(undefined); + const repoSaveSpy = jest + .spyOn(revisionRepo, 'save') + .mockRejectedValue(new Error("shouldn't have been called")); + + const note = Mock.of({}); + const newContent = 'MockContent'; + const yjsState = [0, 1, 2, 3, 4, 5]; + + await service.createAndSaveRevision(note, newContent, yjsState); + expect(createRevisionSpy).toHaveBeenCalledWith( + note, + newContent, + yjsState, + ); + expect(repoSaveSpy).not.toHaveBeenCalled(); + }); + }); }); diff --git a/backend/src/revisions/revisions.service.ts b/backend/src/revisions/revisions.service.ts index 2607dce95..8e37d7541 100644 --- a/backend/src/revisions/revisions.service.ts +++ b/backend/src/revisions/revisions.service.ts @@ -150,6 +150,17 @@ export class RevisionsService { }; } + /** + * Creates (but does not persist(!)) a new {@link Revision} for the given {@link Note}. + * Useful if the revision is saved together with the note in one action. + * + * @async + * @param note The note for which the revision should be created + * @param newContent The new note content + * @param yjsStateVector The yjs state vector that describes the new content + * @return {Revision} the created revision + * @return {undefined} if the revision couldn't be created because e.g. the content hasn't changed + */ async createRevision( note: Note, newContent: string, @@ -185,4 +196,27 @@ export class RevisionsService { tagEntities, ) as Revision; } + + /** + * Creates and saves a new {@link Revision} for the given {@link Note}. + * + * @async + * @param note The note for which the revision should be created + * @param newContent The new note content + * @param yjsStateVector The yjs state vector that describes the new content + */ + async createAndSaveRevision( + note: Note, + newContent: string, + yjsStateVector?: number[], + ): Promise { + const revision = await this.createRevision( + note, + newContent, + yjsStateVector, + ); + if (revision) { + await this.revisionRepository.save(revision); + } + } }