Files
sysops 4151b6f8c5 feat(PROJ-45): IMAP Per-Folder UID-Tracking + UIDVALIDITY-Check
- FolderState: GetFolderState, UpsertFolderState, ListFolderStates, DecideResync
- syncFolder nutzt per-folder UID-Tracking statt globalem highest_uid
- UIDVALIDITY-Check loest automatisch Full-Resync aus
- imap_folder_state Tabelle in initSchema (CREATE TABLE IF NOT EXISTS)
- SetAuditLogger in main.go verdrahtet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 10:49:14 +02:00

83 lines
2.1 KiB
Go

package imap
import "testing"
// TestDecideResync covers the per-folder sync gate introduced by PROJ-45.
// The branches under test correspond directly to the acceptance criteria:
// - first-ever sync (no stored state)
// - normal incremental sync (UIDVALIDITY unchanged)
// - UIDVALIDITY mismatch → full resync + audit event
// - server returns UIDVALIDITY=0 → defensive fallback, keep stored cursor
func TestDecideResync(t *testing.T) {
cases := []struct {
name string
storedUID uint32
storedValid uint32
serverValid uint32
wantFull bool
wantReset bool
wantStart uint32
}{
{
name: "first sync, no stored state",
storedUID: 0,
storedValid: 0,
serverValid: 12345,
wantFull: true,
wantReset: false,
wantStart: 0,
},
{
name: "incremental, validity unchanged",
storedUID: 1500,
storedValid: 12345,
serverValid: 12345,
wantFull: false,
wantReset: false,
wantStart: 1500,
},
{
name: "uidvalidity mismatch triggers full resync + audit",
storedUID: 1500,
storedValid: 12345,
serverValid: 99999,
wantFull: true,
wantReset: true,
wantStart: 0,
},
{
name: "server uidvalidity 0 defensive: keep cursor, no reset",
storedUID: 800,
storedValid: 12345,
serverValid: 0,
wantFull: false,
wantReset: false,
wantStart: 800,
},
{
name: "stored validity 0 (legacy row) does not trigger reset",
storedUID: 0,
storedValid: 0,
serverValid: 7777,
wantFull: true,
wantReset: false,
wantStart: 0,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := DecideResync(tc.storedUID, tc.storedValid, tc.serverValid)
if got.FullResync != tc.wantFull {
t.Errorf("FullResync: got %v want %v", got.FullResync, tc.wantFull)
}
if got.UIDValidReset != tc.wantReset {
t.Errorf("UIDValidReset: got %v want %v", got.UIDValidReset, tc.wantReset)
}
if got.EffectiveStart != tc.wantStart {
t.Errorf("EffectiveStart: got %d want %d", got.EffectiveStart, tc.wantStart)
}
})
}
}