package dyzurbot import ( "context" "errors" "fmt" "path/filepath" "testing" "time" ) func TestLoadConfigDefaults(t *testing.T) { t.Setenv("GOOGLE_SHEETS_ID", "sheet-123") t.Setenv("SHEET_RANGE", "") t.Setenv("ROTA_CACHE_PATH", "") t.Setenv("SYNC_INTERVAL", "") cfg, err := LoadConfig() if err != nil { t.Fatalf("LoadConfig: %v", err) } if cfg.SpreadsheetID != "sheet-123" { t.Errorf("SpreadsheetID: got %q", cfg.SpreadsheetID) } wantRange := fmt.Sprintf("%d!A1:F100", time.Now().Year()) if cfg.SheetRange != wantRange { t.Errorf("SheetRange default: got %q, want %q", cfg.SheetRange, wantRange) } if cfg.CachePath != "rota.json" { t.Errorf("CachePath default: got %q", cfg.CachePath) } if cfg.SyncInterval != 30*time.Minute { t.Errorf("SyncInterval default: got %v", cfg.SyncInterval) } } func TestLoadConfigRequiresSpreadsheetID(t *testing.T) { t.Setenv("GOOGLE_SHEETS_ID", "") if _, err := LoadConfig(); err == nil { t.Fatal("expected error when GOOGLE_SHEETS_ID is missing") } } func TestLoadConfigRejectsBadInterval(t *testing.T) { t.Setenv("GOOGLE_SHEETS_ID", "sheet-123") t.Setenv("SYNC_INTERVAL", "not-a-duration") if _, err := LoadConfig(); err == nil { t.Fatal("expected error for unparseable SYNC_INTERVAL") } } func TestLoadConfigRejectsNonPositiveInterval(t *testing.T) { t.Setenv("GOOGLE_SHEETS_ID", "sheet-123") t.Setenv("SYNC_INTERVAL", "0s") if _, err := LoadConfig(); err == nil { t.Fatal("expected error for non-positive SYNC_INTERVAL") } } func TestSyncSuccessReplacesCache(t *testing.T) { store := NewStore(filepath.Join(t.TempDir(), "rota.json")) fixed := time.Date(2026, 6, 17, 20, 0, 0, 0, time.UTC) sy := NewSyncer(store, func(context.Context) (Rota, error) { return sampleRota(), nil }) sy.now = func() time.Time { return fixed } if err := sy.Sync(context.Background()); err != nil { t.Fatalf("Sync: %v", err) } rota, ts := store.Snapshot() if len(rota.People) != 1 || !ts.Equal(fixed) { t.Errorf("cache not updated: people=%d ts=%v", len(rota.People), ts) } } func TestSyncFailurePreservesCache(t *testing.T) { store := NewStore(filepath.Join(t.TempDir(), "rota.json")) good := time.Date(2026, 6, 17, 20, 0, 0, 0, time.UTC) if err := store.Replace(sampleRota(), good); err != nil { t.Fatalf("seed Replace: %v", err) } wantErr := errors.New("sheet unreachable") sy := NewSyncer(store, func(context.Context) (Rota, error) { return Rota{}, wantErr }) if err := sy.Sync(context.Background()); !errors.Is(err, wantErr) { t.Fatalf("Sync error: got %v, want %v", err, wantErr) } rota, ts := store.Snapshot() if len(rota.People) != 1 || !ts.Equal(good) { t.Errorf("cache should be untouched: people=%d ts=%v", len(rota.People), ts) } }