Resilience: - RetryTransport with circuit breaker for 429/5xx resilience - Exponential backoff with jitter, respects Retry-After headers - Circuit breaker auto-resets after 30s of successful requests Performance: - Concurrent gmail thread fetching (fixes N+1 query pattern) - Bounded concurrency with semaphore (max 10 parallel) New calendar commands: - colors: list available event/calendar colors - conflicts: check availability across calendars - search: find events by text query - time: show current time in multiple timezones New gmail commands: - autoforward: get/enable/disable auto-forwarding - delegates: list/add/remove mail delegation - filters: list/create/delete inbox filters - forwarding: manage forwarding addresses - sendas: manage send-as aliases - vacation: get/enable/disable vacation responder - batch: bulk operations (mark-read, archive, label, delete) - watch: Pub/Sub push with webhook forwarding New services: - Sheets: read/write/append spreadsheet data - Tasks: manage tasklists and tasks Developer experience: - Shell completion (bash, zsh, fish, powershell) - version command with build info - --debug flag for verbose logging - lefthook for pre-commit hooks Documentation: - Expanded README with examples - Gmail watch/Pub/Sub guide (docs/watch.md) - Architecture spec (docs/spec.md) - Release process (docs/RELEASING.md)
69 lines
1.3 KiB
Go
69 lines
1.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestAutoForwardCommandExists(t *testing.T) {
|
|
// Unit tests for the actual API call live in integration; here we just ensure
|
|
// the command exists and is properly structured. (Compile-time coverage.)
|
|
_ = newGmailAutoForwardCmd
|
|
_ = newGmailAutoForwardGetCmd
|
|
_ = newGmailAutoForwardUpdateCmd
|
|
}
|
|
|
|
func TestValidateDisposition(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value string
|
|
isValid bool
|
|
}{
|
|
{
|
|
name: "leaveInInbox is valid",
|
|
value: "leaveInInbox",
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "archive is valid",
|
|
value: "archive",
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "trash is valid",
|
|
value: "trash",
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "markRead is valid",
|
|
value: "markRead",
|
|
isValid: true,
|
|
},
|
|
{
|
|
name: "invalid value",
|
|
value: "deleteForever",
|
|
isValid: false,
|
|
},
|
|
{
|
|
name: "empty string",
|
|
value: "",
|
|
isValid: false,
|
|
},
|
|
}
|
|
|
|
validDispositions := map[string]bool{
|
|
"leaveInInbox": true,
|
|
"archive": true,
|
|
"trash": true,
|
|
"markRead": true,
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := validDispositions[tt.value]
|
|
if got != tt.isValid {
|
|
t.Errorf("disposition %q: got valid=%v, want valid=%v", tt.value, got, tt.isValid)
|
|
}
|
|
})
|
|
}
|
|
}
|