diff --git a/packages/lib/services/ReportService.test.ts b/packages/lib/services/ReportService.test.ts index fb65c6799..54bd9f1cc 100644 --- a/packages/lib/services/ReportService.test.ts +++ b/packages/lib/services/ReportService.test.ts @@ -1,22 +1,25 @@ -import SyncTargetRegistry from '../SyncTargetRegistry'; import { _ } from '../locale'; import ReportService, { ReportSection } from './ReportService'; -import { createNTestNotes, decryptionWorker, setupDatabaseAndSynchronizer, switchClient, synchronizerStart } from '../testing/test-utils'; +import { createNTestNotes, decryptionWorker, setupDatabaseAndSynchronizer, supportDir, switchClient, syncTargetId, synchronizer, synchronizerStart } from '../testing/test-utils'; import Folder from '../models/Folder'; import BaseItem from '../models/BaseItem'; import DecryptionWorker from './DecryptionWorker'; +import Note from '../models/Note'; +import shim from '../shim'; -const getSectionsWithTitle = (report: ReportSection[], title: string) => { - return report.filter(section => section.title === title); +const firstSectionWithTitle = (report: ReportSection[], title: string) => { + const sections = report.filter(section => section.title === title); + if (sections.length === 0) return null; + return sections[0]; }; const getCannotSyncSection = (report: ReportSection[]) => { - return getSectionsWithTitle(report, _('Items that cannot be synchronised'))[0]; + return firstSectionWithTitle(report, _('Items that cannot be synchronised')); }; const getIgnoredSection = (report: ReportSection[]) => { - return getSectionsWithTitle(report, _('Ignored items that cannot be synchronised'))[0]; + return firstSectionWithTitle(report, _('Ignored items that cannot be synchronised')); }; const sectionBodyToText = (section: ReportSection) => { @@ -32,8 +35,8 @@ const sectionBodyToText = (section: ReportSection) => { describe('ReportService', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); + await setupDatabaseAndSynchronizer(2); await switchClient(1); - await synchronizerStart(); // For compatibility with code that calls DecryptionWorker.instance() DecryptionWorker.instance_ = decryptionWorker(); }); @@ -43,15 +46,14 @@ describe('ReportService', () => { const noteCount = 5; const testNotes = await createNTestNotes(noteCount, folder); await synchronizerStart(); - const syncTargetId = SyncTargetRegistry.nameToId('memory'); const disabledReason = 'Test reason'; for (const testNote of testNotes) { - await BaseItem.saveSyncDisabled(syncTargetId, testNote, disabledReason); + await BaseItem.saveSyncDisabled(syncTargetId(), testNote, disabledReason); } const service = new ReportService(); - let report = await service.status(syncTargetId); + let report = await service.status(syncTargetId()); // Items should all initially be listed as "cannot be synchronized", but should be ignorable. const unsyncableSection = getCannotSyncSection(report); @@ -65,16 +67,16 @@ describe('ReportService', () => { expect(sectionBodyToText(unsyncableSection)).toContain(disabledReason); // Ignore all - expect(await BaseItem.syncDisabledItemsCount(syncTargetId)).toBe(noteCount); - expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId)).toBe(noteCount); + expect(await BaseItem.syncDisabledItemsCount(syncTargetId())).toBe(noteCount); + expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId())).toBe(noteCount); for (const item of ignorableItems) { await item.ignoreHandler(); } - expect(await BaseItem.syncDisabledItemsCount(syncTargetId)).toBe(0); - expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId)).toBe(noteCount); + expect(await BaseItem.syncDisabledItemsCount(syncTargetId())).toBe(0); + expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId())).toBe(noteCount); await synchronizerStart(); - report = await service.status(syncTargetId); + report = await service.status(syncTargetId()); // Should now be in the ignored section const ignoredSection = getIgnoredSection(report); @@ -92,7 +94,7 @@ describe('ReportService', () => { } } // Should have the correct number of ignored items - expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId)).toBe(ignoredItemCount); + expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId())).toBe(ignoredItemCount); expect(ignoredItemCount).toBe(noteCount); // Clicking "retry" should un-ignore @@ -103,6 +105,58 @@ describe('ReportService', () => { break; } } - expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId)).toBe(noteCount - 1); + expect(await BaseItem.syncDisabledItemsCountIncludingIgnored(syncTargetId())).toBe(noteCount - 1); + }); + + it('should support ignoring sync errors for resources that failed to download', async () => { + const createAttachmentDownloadError = async () => { + await switchClient(2); + + const note1 = await Note.save({ title: 'note' }); + await shim.attachFileToNote(note1, `${supportDir}/photo.jpg`); + await synchronizerStart(); + + await switchClient(1); + + const previousMax = synchronizer().maxResourceSize_; + synchronizer().maxResourceSize_ = 1; + await synchronizerStart(); + synchronizer().maxResourceSize_ = previousMax; + }; + await createAttachmentDownloadError(); + + const service = new ReportService(); + let report = await service.status(syncTargetId()); + + const unsyncableSection = getCannotSyncSection(report); + expect(sectionBodyToText(unsyncableSection)).toContain('could not be downloaded'); + + // Item for the download error should be ignorable + const ignorableItems = []; + for (const item of unsyncableSection.body) { + if (typeof item === 'object' && item.canIgnore) { + ignorableItems.push(item); + } + } + expect(ignorableItems).toHaveLength(1); + + await ignorableItems[0].ignoreHandler(); + + // Should now be ignored. + report = await service.status(syncTargetId()); + const ignoredItem = getIgnoredSection(report).body.find(item => typeof item === 'object' && item.canRetry === true); + expect(ignoredItem).not.toBeFalsy(); + + // Type narrowing + if (typeof ignoredItem === 'string') throw new Error('should be an object'); + + // Should be possible to retry + await ignoredItem.retryHandler(); + await synchronizerStart(); + + // Should be fixed after retrying + report = await service.status(syncTargetId()); + expect(getIgnoredSection(report)).toBeNull(); + expect(getCannotSyncSection(report)).toBeNull(); }); }); diff --git a/packages/lib/services/ReportService.ts b/packages/lib/services/ReportService.ts index 0d742d4c8..8b02e81fc 100644 --- a/packages/lib/services/ReportService.ts +++ b/packages/lib/services/ReportService.ts @@ -194,16 +194,18 @@ export default class ReportService { msg = _('Item "%s" could not be downloaded: %s', row.syncInfo.item_id, row.syncInfo.sync_disabled_reason); } + // row.item may be undefined when location !== SYNC_ITEM_LOCATION_LOCAL + const item = { type_: row.syncInfo.item_type, id: row.syncInfo.item_id }; section.body.push({ text: msg, canRetry: true, canRetryType: CanRetryType.ItemSync, retryHandler: async () => { - await BaseItem.saveSyncEnabled(row.item.type_, row.item.id); + await BaseItem.saveSyncEnabled(item.type_, item.id); }, canIgnore: !row.warning_ignored, ignoreHandler: async () => { - await BaseItem.ignoreItemSyncWarning(syncTarget, row.item); + await BaseItem.ignoreItemSyncWarning(syncTarget, item); }, }); };