1- import { describe , it , expect , jest , beforeEach } from "@jest/globals" ;
1+ import { describe , it , expect , jest } from "@jest/globals" ;
22import { TFile } from "obsidian" ;
33
44import { AutoArchiveService } from "../../../src/services/AutoArchiveService" ;
@@ -17,6 +17,17 @@ jest.mock("obsidian", () => ({
1717 } ,
1818} ) ) ;
1919
20+ const createGoogleCleanupEnabledPlugin = ( ) =>
21+ PluginFactory . createMockPlugin ( {
22+ settings : {
23+ ...PluginFactory . createMockPlugin ( ) . settings ,
24+ googleCalendarExport : {
25+ enabled : true ,
26+ syncOnTaskDelete : true ,
27+ } ,
28+ } ,
29+ } ) ;
30+
2031describe ( "Google Calendar archive reliability" , ( ) => {
2132 it ( "preserves the Google Calendar event ID when deletion fails so cleanup can be retried" , async ( ) => {
2233 const frontmatter : Record < string , any > = { } ;
@@ -97,7 +108,7 @@ describe("Google Calendar archive reliability", () => {
97108 } ) ;
98109
99110 it ( "keeps an auto-archive queue item pending when Google cleanup is still incomplete after archiving" , async ( ) => {
100- const plugin = PluginFactory . createMockPlugin ( ) ;
111+ const plugin = createGoogleCleanupEnabledPlugin ( ) ;
101112 plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
102113 plugin . taskService . toggleArchive = jest . fn ( ) ;
103114 plugin . taskCalendarSyncService = {
@@ -136,7 +147,7 @@ describe("Google Calendar archive reliability", () => {
136147 } ) ;
137148
138149 it ( "retries Google cleanup for archived tasks that still have calendar links" , async ( ) => {
139- const plugin = PluginFactory . createMockPlugin ( ) ;
150+ const plugin = createGoogleCleanupEnabledPlugin ( ) ;
140151 plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
141152 plugin . taskService . toggleArchive = jest . fn ( ) ;
142153 plugin . taskCalendarSyncService = {
@@ -168,4 +179,178 @@ describe("Google Calendar archive reliability", () => {
168179 ) ;
169180 expect ( plugin . taskService . toggleArchive ) . not . toHaveBeenCalled ( ) ;
170181 } ) ;
182+
183+ it ( "persists the archived path in the retry queue when cleanup remains pending after an archive-folder move" , async ( ) => {
184+ const plugin = createGoogleCleanupEnabledPlugin ( ) ;
185+ const initialItem = {
186+ taskPath : "TaskNotes/Tasks/archive-me.md" ,
187+ statusChangeTimestamp : 0 ,
188+ archiveAfterTimestamp : 0 ,
189+ statusValue : "done" ,
190+ } ;
191+ const pluginData = { autoArchiveQueue : [ initialItem ] } ;
192+ plugin . loadData = jest . fn ( ) . mockResolvedValue ( pluginData ) ;
193+ plugin . saveData = jest . fn ( ) . mockResolvedValue ( undefined ) ;
194+ plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
195+ plugin . taskService . toggleArchive = jest . fn ( ) ;
196+ plugin . taskCalendarSyncService = {
197+ isEnabled : jest . fn ( ) . mockReturnValue ( true ) ,
198+ deleteTaskFromCalendar : jest . fn ( ) . mockResolvedValue ( true ) ,
199+ } ;
200+
201+ const autoArchiveService = new AutoArchiveService ( plugin ) ;
202+ const currentTask : TaskInfo = TaskFactory . createTask ( {
203+ path : "TaskNotes/Tasks/archive-me.md" ,
204+ status : "done" ,
205+ archived : false ,
206+ googleCalendarEventId : "master-event-id" ,
207+ } ) ;
208+ const archivedTask : TaskInfo = {
209+ ...currentTask ,
210+ path : "TaskNotes/Archive/archive-me.md" ,
211+ archived : true ,
212+ tags : [ "task" , "archived" ] ,
213+ } ;
214+
215+ plugin . cacheManager . getTaskByPath . mockResolvedValue ( currentTask ) ;
216+ plugin . taskService . toggleArchive . mockResolvedValue ( archivedTask ) ;
217+
218+ await ( autoArchiveService as any ) . processQueue ( ) ;
219+
220+ expect ( plugin . saveData ) . toHaveBeenCalledWith ( {
221+ autoArchiveQueue : [ { ...initialItem , taskPath : archivedTask . path } ] ,
222+ } ) ;
223+ } ) ;
224+
225+ it ( "keeps archived tasks in the retry queue until calendar sync is ready" , async ( ) => {
226+ const plugin = createGoogleCleanupEnabledPlugin ( ) ;
227+ const archivedItem = {
228+ taskPath : "TaskNotes/Archive/archive-me.md" ,
229+ statusChangeTimestamp : 0 ,
230+ archiveAfterTimestamp : 0 ,
231+ statusValue : "done" ,
232+ } ;
233+ const pluginData = { autoArchiveQueue : [ archivedItem ] } ;
234+ plugin . loadData = jest . fn ( ) . mockResolvedValue ( pluginData ) ;
235+ plugin . saveData = jest . fn ( ) . mockResolvedValue ( undefined ) ;
236+ plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
237+ plugin . taskService . toggleArchive = jest . fn ( ) ;
238+ plugin . taskCalendarSyncService = undefined ;
239+
240+ const autoArchiveService = new AutoArchiveService ( plugin ) ;
241+ const archivedTask : TaskInfo = TaskFactory . createTask ( {
242+ path : archivedItem . taskPath ,
243+ status : "done" ,
244+ archived : true ,
245+ tags : [ "task" , "archived" ] ,
246+ googleCalendarEventId : "master-event-id" ,
247+ } ) ;
248+
249+ plugin . cacheManager . getTaskByPath . mockResolvedValue ( archivedTask ) ;
250+
251+ await ( autoArchiveService as any ) . processQueue ( ) ;
252+
253+ expect ( plugin . saveData ) . toHaveBeenCalledWith ( {
254+ autoArchiveQueue : [ archivedItem ] ,
255+ } ) ;
256+ expect ( plugin . taskService . toggleArchive ) . not . toHaveBeenCalled ( ) ;
257+ } ) ;
258+
259+ it ( "drops archived tasks from the retry queue when Google cleanup is intentionally disabled" , async ( ) => {
260+ const plugin = PluginFactory . createMockPlugin ( {
261+ settings : {
262+ ...createGoogleCleanupEnabledPlugin ( ) . settings ,
263+ googleCalendarExport : {
264+ enabled : false ,
265+ syncOnTaskDelete : true ,
266+ } ,
267+ } ,
268+ } ) ;
269+ const archivedItem = {
270+ taskPath : "TaskNotes/Archive/archive-me.md" ,
271+ statusChangeTimestamp : 0 ,
272+ archiveAfterTimestamp : 0 ,
273+ statusValue : "done" ,
274+ } ;
275+ const pluginData = { autoArchiveQueue : [ archivedItem ] } ;
276+ plugin . loadData = jest . fn ( ) . mockResolvedValue ( pluginData ) ;
277+ plugin . saveData = jest . fn ( ) . mockResolvedValue ( undefined ) ;
278+ plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
279+ plugin . taskService . toggleArchive = jest . fn ( ) ;
280+ plugin . taskCalendarSyncService = {
281+ isEnabled : jest . fn ( ) . mockReturnValue ( false ) ,
282+ deleteTaskFromCalendar : jest . fn ( ) . mockResolvedValue ( true ) ,
283+ } ;
284+
285+ const autoArchiveService = new AutoArchiveService ( plugin ) ;
286+ const archivedTask : TaskInfo = TaskFactory . createTask ( {
287+ path : archivedItem . taskPath ,
288+ status : "done" ,
289+ archived : true ,
290+ tags : [ "task" , "archived" ] ,
291+ googleCalendarEventId : "master-event-id" ,
292+ } ) ;
293+
294+ plugin . cacheManager . getTaskByPath . mockResolvedValue ( archivedTask ) ;
295+
296+ await ( autoArchiveService as any ) . processQueue ( ) ;
297+
298+ expect ( plugin . saveData ) . toHaveBeenCalledWith ( {
299+ autoArchiveQueue : [ ] ,
300+ } ) ;
301+ expect ( plugin . taskCalendarSyncService . deleteTaskFromCalendar ) . not . toHaveBeenCalled ( ) ;
302+ expect ( plugin . taskService . toggleArchive ) . not . toHaveBeenCalled ( ) ;
303+ } ) ;
304+
305+ it ( "drops newly archived tasks from the retry queue when Google cleanup is intentionally disabled" , async ( ) => {
306+ const plugin = PluginFactory . createMockPlugin ( {
307+ settings : {
308+ ...createGoogleCleanupEnabledPlugin ( ) . settings ,
309+ googleCalendarExport : {
310+ enabled : false ,
311+ syncOnTaskDelete : true ,
312+ } ,
313+ } ,
314+ } ) ;
315+ const initialItem = {
316+ taskPath : "TaskNotes/Tasks/archive-me.md" ,
317+ statusChangeTimestamp : 0 ,
318+ archiveAfterTimestamp : 0 ,
319+ statusValue : "done" ,
320+ } ;
321+ const pluginData = { autoArchiveQueue : [ initialItem ] } ;
322+ plugin . loadData = jest . fn ( ) . mockResolvedValue ( pluginData ) ;
323+ plugin . saveData = jest . fn ( ) . mockResolvedValue ( undefined ) ;
324+ plugin . cacheManager . getTaskByPath = jest . fn ( ) ;
325+ plugin . taskService . toggleArchive = jest . fn ( ) ;
326+ plugin . taskCalendarSyncService = {
327+ isEnabled : jest . fn ( ) . mockReturnValue ( false ) ,
328+ deleteTaskFromCalendar : jest . fn ( ) . mockResolvedValue ( true ) ,
329+ } ;
330+
331+ const autoArchiveService = new AutoArchiveService ( plugin ) ;
332+ const currentTask : TaskInfo = TaskFactory . createTask ( {
333+ path : initialItem . taskPath ,
334+ status : "done" ,
335+ archived : false ,
336+ googleCalendarEventId : "master-event-id" ,
337+ } ) ;
338+ const archivedTask : TaskInfo = {
339+ ...currentTask ,
340+ path : "TaskNotes/Archive/archive-me.md" ,
341+ archived : true ,
342+ tags : [ "task" , "archived" ] ,
343+ } ;
344+
345+ plugin . cacheManager . getTaskByPath . mockResolvedValue ( currentTask ) ;
346+ plugin . taskService . toggleArchive . mockResolvedValue ( archivedTask ) ;
347+
348+ await ( autoArchiveService as any ) . processQueue ( ) ;
349+
350+ expect ( plugin . saveData ) . toHaveBeenCalledWith ( {
351+ autoArchiveQueue : [ ] ,
352+ } ) ;
353+ expect ( plugin . taskService . toggleArchive ) . toHaveBeenCalledWith ( currentTask ) ;
354+ expect ( plugin . taskCalendarSyncService . deleteTaskFromCalendar ) . not . toHaveBeenCalled ( ) ;
355+ } ) ;
171356} ) ;
0 commit comments