Compare commits

...

1 Commits

Author SHA1 Message Date
cadcfa7882 Add autosync manager serialization tests 2026-01-18 18:30:59 +00:00
2 changed files with 140 additions and 6 deletions

View File

@@ -0,0 +1,112 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { AutoSyncManager } from '../autoSyncMiddleware'
import { syncToFlaskBulk } from '../../slices/syncSlice'
vi.mock('../../slices/syncSlice', () => ({
syncToFlaskBulk: vi.fn(() => ({ type: 'sync/syncToFlaskBulk' })),
checkFlaskConnection: vi.fn(() => ({ type: 'sync/checkConnection' })),
}))
type Deferred<T> = {
promise: Promise<T>
resolve: (value: T) => void
reject: (error?: unknown) => void
}
const createDeferred = <T,>(): Deferred<T> => {
let resolve!: (value: T) => void
let reject!: (error?: unknown) => void
const promise = new Promise<T>((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
describe('AutoSyncManager', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('serializes syncs and runs one pending sync after completion', async () => {
const manager = new AutoSyncManager()
const deferreds = [createDeferred<void>(), createDeferred<void>()]
const dispatch = vi
.fn()
.mockImplementation(() => deferreds.shift()?.promise ?? Promise.resolve())
manager.setDispatch(dispatch)
const firstSync = manager.syncNow()
const secondSync = manager.syncNow()
expect(dispatch).toHaveBeenCalledTimes(1)
deferreds[0].resolve()
await Promise.resolve()
expect(dispatch).toHaveBeenCalledTimes(2)
deferreds[1].resolve()
await firstSync
await secondSync
})
it('resets changeCounter after a successful sync', async () => {
const manager = new AutoSyncManager()
const dispatch = vi.fn().mockResolvedValue(undefined)
manager.setDispatch(dispatch)
manager.trackChange()
manager.trackChange()
expect(manager.getStatus().changeCounter).toBe(2)
await manager.syncNow()
expect(manager.getStatus().changeCounter).toBe(0)
expect(dispatch).toHaveBeenCalledTimes(1)
})
it('coalesces multiple pending sync requests into one run', async () => {
const manager = new AutoSyncManager()
const deferreds = [createDeferred<void>(), createDeferred<void>()]
const dispatch = vi
.fn()
.mockImplementation(() => deferreds.shift()?.promise ?? Promise.resolve())
manager.setDispatch(dispatch)
const firstSync = manager.syncNow()
const secondSync = manager.syncNow()
const thirdSync = manager.syncNow()
expect(dispatch).toHaveBeenCalledTimes(1)
deferreds[0].resolve()
await Promise.resolve()
expect(dispatch).toHaveBeenCalledTimes(2)
deferreds[1].resolve()
await firstSync
await secondSync
await thirdSync
expect(dispatch).toHaveBeenCalledTimes(2)
})
it('dispatches the sync thunk when performing a sync', async () => {
const manager = new AutoSyncManager()
const dispatch = vi.fn().mockResolvedValue(undefined)
manager.setDispatch(dispatch)
await manager.syncNow()
expect(dispatch).toHaveBeenCalledWith(syncToFlaskBulk())
})
})

View File

@@ -9,7 +9,7 @@ interface AutoSyncConfig {
maxQueueSize: number maxQueueSize: number
} }
class AutoSyncManager { export class AutoSyncManager {
private config: AutoSyncConfig = { private config: AutoSyncConfig = {
enabled: false, enabled: false,
intervalMs: 30000, intervalMs: 30000,
@@ -21,6 +21,8 @@ class AutoSyncManager {
private lastSyncTime = 0 private lastSyncTime = 0
private changeCounter = 0 private changeCounter = 0
private dispatch: any = null private dispatch: any = null
private syncInFlight: Promise<void> | null = null
private pendingSync = false
configure(config: Partial<AutoSyncConfig>) { configure(config: Partial<AutoSyncConfig>) {
this.config = { ...this.config, ...config } this.config = { ...this.config, ...config }
@@ -69,12 +71,32 @@ class AutoSyncManager {
private async performSync() { private async performSync() {
if (!this.dispatch) return if (!this.dispatch) return
if (this.syncInFlight) {
this.pendingSync = true
return
}
const syncPromise = (async () => {
try {
await this.dispatch(syncToFlaskBulk())
this.lastSyncTime = Date.now()
this.changeCounter = 0
} catch (error) {
console.error('[AutoSync] Sync failed:', error)
}
})()
this.syncInFlight = syncPromise
try { try {
await this.dispatch(syncToFlaskBulk()) await syncPromise
this.lastSyncTime = Date.now() } finally {
this.changeCounter = 0 this.syncInFlight = null
} catch (error) { }
console.error('[AutoSync] Sync failed:', error)
if (this.pendingSync) {
this.pendingSync = false
await this.performSync()
} }
} }