From a0d2a8465690038be6a748cbafadba7400ab0733 Mon Sep 17 00:00:00 2001 From: Tamara deMent Date: Thu, 12 Feb 2026 13:19:34 -0500 Subject: [PATCH 1/3] Collect errors --- cmd/sync_ooo_to_gcal/main.go | 2 ++ core/calendar.go | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/sync_ooo_to_gcal/main.go b/cmd/sync_ooo_to_gcal/main.go index 1c08300..c36e4b0 100644 --- a/cmd/sync_ooo_to_gcal/main.go +++ b/cmd/sync_ooo_to_gcal/main.go @@ -6,6 +6,7 @@ import ( "encoding/json" "flag" "fmt" + "log" "os" "strings" "time" @@ -163,6 +164,7 @@ func (e *Event) Run(ctx context.Context) { calendarIDs := []string{"primary"} if err := core.InsertOOOEvents(ctx, jwtCfg, env.Requests, calendarIDs); err != nil { + log.Printf("Sync completed with errors: %v", err) core.Die("insert events: %v", err) } diff --git a/core/calendar.go b/core/calendar.go index 88d30da..658d908 100644 --- a/core/calendar.go +++ b/core/calendar.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "fmt" "log" "time" @@ -12,22 +13,27 @@ import ( ) func InsertOOOEvents(ctx context.Context, jwtCfg *jwt.Config, requests []ClockifyRequest, calendarIDs []string) error { + var errs []error + for _, r := range requests { // Load user's local timezone loc, err := time.LoadLocation(r.UserTimeZone) if err != nil { log.Printf("skip %s: unknown tz %q: %v", r.ID, r.UserTimeZone, err) + errs = append(errs, fmt.Errorf("req=%s user=%s: unknown tz %q: %w", r.ID, r.UserEmail, r.UserTimeZone, err)) continue } startUTC, err := ParseTimeAny(r.TimeOffPeriod.Period.Start) if err != nil { log.Printf("skip %s: bad period.start: %v", r.ID, err) + errs = append(errs, fmt.Errorf("req=%s user=%s: bad period.start: %w", r.ID, r.UserEmail, err)) continue } endUTC, err := ParseTimeAny(r.TimeOffPeriod.Period.End) if err != nil { log.Printf("skip %s: bad period.end: %v", r.ID, err) + errs = append(errs, fmt.Errorf("req=%s user=%s: bad period.end: %w", r.ID, r.UserEmail, err)) continue } @@ -55,6 +61,7 @@ func InsertOOOEvents(ctx context.Context, jwtCfg *jwt.Config, requests []Clockif srv, err := calendar.NewService(ctx, option.WithHTTPClient(client)) if err != nil { log.Printf("user %s: calendar service error: %v", r.UserEmail, err) + errs = append(errs, fmt.Errorf("req=%s user=%s: calendar service error: %w", r.ID, r.UserEmail, err)) continue } @@ -85,6 +92,7 @@ func InsertOOOEvents(ctx context.Context, jwtCfg *jwt.Config, requests []Clockif if err != nil { log.Printf("lookup %s (user=%s cal=%s) failed: %v", r.ID, r.UserEmail, calID, err) + errs = append(errs, fmt.Errorf("req=%s user=%s cal=%s: lookup failed: %w", r.ID, r.UserEmail, calID, err)) continue } @@ -108,6 +116,7 @@ func InsertOOOEvents(ctx context.Context, jwtCfg *jwt.Config, requests []Clockif if err != nil { log.Printf("insert %s (user=%s cal=%s) failed: %v", r.ID, r.UserEmail, calID, err) + errs = append(errs, fmt.Errorf("req=%s user=%s cal=%s: insert failed: %w", r.ID, r.UserEmail, calID, err)) continue } @@ -118,5 +127,6 @@ func InsertOOOEvents(ctx context.Context, jwtCfg *jwt.Config, requests []Clockif } } - return nil + + return errors.Join(errs...) } From 69702250acbf1bad00af883ec47d9792e102fd66 Mon Sep 17 00:00:00 2001 From: Tamara deMent Date: Thu, 12 Feb 2026 17:04:12 -0500 Subject: [PATCH 2/3] Add unit tests for invalid timezone, start date & end date cases --- core/calendar_test.go | 35 ++++++++++++++++++++++++++++++++++ core/clockify_fixtures_test.go | 28 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 core/calendar_test.go create mode 100644 core/clockify_fixtures_test.go diff --git a/core/calendar_test.go b/core/calendar_test.go new file mode 100644 index 0000000..c943a5a --- /dev/null +++ b/core/calendar_test.go @@ -0,0 +1,35 @@ +package core + +import ( + "context" + "errors" + "testing" + + "golang.org/x/oauth2/jwt" +) + +func TestInsertOOOEvents_CollectsErrors(t *testing.T) { + ctx := context.Background() + jwtCfg := &jwt.Config{} + calendarIDs := []string{"primary"} + + reqs := []ClockifyRequest{ + fixtureBadTimeZone(), + fixtureInvalidStartDate(), + fixtureInvalidEndDate(), + } + + err := InsertOOOEvents(ctx, jwtCfg, reqs, calendarIDs) + if err == nil { + t.Fatalf("expected non-nil error") + } + + var multi interface{ Unwrap() []error } + if !errors.As(err, &multi) { + t.Fatalf("expected joined error with Unwrap() []error, got: %T", err) + } + + if got := len(multi.Unwrap()); got != len(reqs) { + t.Fatalf("expected %d collected errors, got %d", len(reqs), got) + } +} diff --git a/core/clockify_fixtures_test.go b/core/clockify_fixtures_test.go new file mode 100644 index 0000000..25551f2 --- /dev/null +++ b/core/clockify_fixtures_test.go @@ -0,0 +1,28 @@ +package core + +import "time" + +func mkReq(id, tz, start, end string) ClockifyRequest { + var r ClockifyRequest + + r.ID = id + r.UserEmail = "fixture@example.com" + r.UserTimeZone = tz + r.CreatedAt = time.Date(2025, 12, 1, 12, 0, 0, 0, time.UTC).Format(time.RFC3339) + r.PolicyName = "Vacation" + + r.TimeOffPeriod.Period.Start = start + r.TimeOffPeriod.Period.End = end + + return r +} + +func fixtureBadTimeZone() ClockifyRequest { + return mkReq("fixture-bad-tz", "Not/A_Timezone", "2025-12-10T00:00:00Z", "2025-12-10T23:59:59Z") +} +func fixtureInvalidStartDate() ClockifyRequest { + return mkReq("fixture-bad-start", "America/New_York", "not-a-date", "2025-12-10T23:59:59Z") +} +func fixtureInvalidEndDate() ClockifyRequest { + return mkReq("fixture-bad-end", "America/New_York", "2025-12-10T00:00:00Z", "not-a-date") +} From 9634ac93fab37140d8ad70fede892f12a0625a7c Mon Sep 17 00:00:00 2001 From: Tamara deMent Date: Fri, 13 Feb 2026 12:32:50 -0500 Subject: [PATCH 3/3] Refactors test assertion for clarity --- core/calendar_test.go | 14 ++++++++++---- core/clockify_fixtures_test.go | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/calendar_test.go b/core/calendar_test.go index c943a5a..6daa612 100644 --- a/core/calendar_test.go +++ b/core/calendar_test.go @@ -24,12 +24,18 @@ func TestInsertOOOEvents_CollectsErrors(t *testing.T) { t.Fatalf("expected non-nil error") } - var multi interface{ Unwrap() []error } - if !errors.As(err, &multi) { + var multiErrors interface{ Unwrap() []error } + if !errors.As(err, &multiErrors) { t.Fatalf("expected joined error with Unwrap() []error, got: %T", err) } - if got := len(multi.Unwrap()); got != len(reqs) { - t.Fatalf("expected %d collected errors, got %d", len(reqs), got) + collectedErrCount := len(multiErrors.Unwrap()) + expectedErrCount := len(reqs) + + if collectedErrCount != expectedErrCount { + t.Fatalf("expected %d collected errors, got %d", + expectedErrCount, + collectedErrCount, + ) } } diff --git a/core/clockify_fixtures_test.go b/core/clockify_fixtures_test.go index 25551f2..4faaac1 100644 --- a/core/clockify_fixtures_test.go +++ b/core/clockify_fixtures_test.go @@ -2,7 +2,7 @@ package core import "time" -func mkReq(id, tz, start, end string) ClockifyRequest { +func makeRequest(id, tz, start, end string) ClockifyRequest { var r ClockifyRequest r.ID = id @@ -18,11 +18,11 @@ func mkReq(id, tz, start, end string) ClockifyRequest { } func fixtureBadTimeZone() ClockifyRequest { - return mkReq("fixture-bad-tz", "Not/A_Timezone", "2025-12-10T00:00:00Z", "2025-12-10T23:59:59Z") + return makeRequest("fixture-bad-time-zone", "Not/A_Timezone", "2025-12-10T00:00:00Z", "2025-12-10T23:59:59Z") } func fixtureInvalidStartDate() ClockifyRequest { - return mkReq("fixture-bad-start", "America/New_York", "not-a-date", "2025-12-10T23:59:59Z") + return makeRequest("fixture-bad-start", "America/New_York", "not-a-date", "2025-12-10T23:59:59Z") } func fixtureInvalidEndDate() ClockifyRequest { - return mkReq("fixture-bad-end", "America/New_York", "2025-12-10T00:00:00Z", "not-a-date") + return makeRequest("fixture-bad-end", "America/New_York", "2025-12-10T00:00:00Z", "not-a-date") }