Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ description: A Helm chart for Kubernetes

type: application

version: 0.2.5
appVersion: "v0.2.5"
version: 0.2.6
appVersion: "v0.2.6"
2 changes: 1 addition & 1 deletion internal/availability/availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ func loadLocation(name string) *time.Location {

func blockIsFree(events []calendar.ParsedEvent, start, end time.Time, loc *time.Location) bool {
for _, event := range events {
if event.Cancelled {
if event.Cancelled || !event.Busy {
continue
}

Expand Down
37 changes: 37 additions & 0 deletions internal/availability/availability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,43 @@ END:VCALENDAR`
}
}

func TestCompute_IgnoresTransparentEvents(t *testing.T) {
blocks := testBlocks(t)
body := `BEGIN:VCALENDAR
VERSION:2.0
X-WR-TIMEZONE:Europe/London
BEGIN:VEVENT
UID:free-morning
DTSTART:20260406T090000
DTEND:20260406T103000
SUMMARY:Free morning
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR`

opts := ComputeOptions{
WorkingHours: testWorkingHours(t),
HolidayDates: []string{"2026-04-06"},
ExcludeEnglandBankHolidays: true,
Now: londonTime(t, 2026, 4, 6, 8, 30),
}

entries, err := Compute(body, "Europe/London", blocks, opts)
if err != nil {
t.Fatalf("Compute: %v", err)
}
if len(entries) == 0 {
t.Fatal("expected availability entries")
}
// With the transparent event, the Morning block should still be available.
if got, want := entries[0].Date, "2026-04-06"; got != want {
t.Fatalf("first available date: got %q, want %q", got, want)
}
if got, want := entries[0].Block, "Morning"; got != want {
t.Fatalf("first available block: got %q, want %q", got, want)
}
}

func TestHandler_AuthorizationAndResponse(t *testing.T) {
st := newTestStore(t)
blocks := testBlocks(t)
Expand Down
11 changes: 11 additions & 0 deletions internal/calendar/ical.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type ParsedEvent struct {
StartTime time.Time
EndTime time.Time
Cancelled bool
Busy bool
}

// ParsedCalendar is the parsed representation of an iCal feed.
Expand Down Expand Up @@ -97,6 +98,7 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
startTime time.Time
endTime time.Time
cancelled bool
busy bool
rrule string
}

Expand All @@ -115,6 +117,11 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
status := event.GetProperty(ics.ComponentPropertyStatus)
cancelled := status != nil && status.Value == "CANCELLED"

busy := true
if transp := event.GetProperty(ics.ComponentPropertyTransp); transp != nil {
busy = strings.ToUpper(transp.Value) == "OPAQUE"
}

startAt, err := event.GetStartAt()
if err != nil {
continue
Expand All @@ -133,6 +140,7 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
startTime: startAt,
endTime: endAt,
cancelled: cancelled,
busy: busy,
}
if rruleProp := event.GetProperty(ics.ComponentPropertyRrule); rruleProp != nil {
base.rrule = rruleProp.Value
Expand All @@ -151,6 +159,7 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
StartTime: base.startTime,
EndTime: base.endTime,
Cancelled: base.cancelled,
Busy: base.busy,
})
}
continue
Expand All @@ -174,6 +183,7 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
StartTime: inst,
EndTime: endAt,
Cancelled: base.cancelled,
Busy: base.busy,
})
}
continue
Expand All @@ -187,6 +197,7 @@ func ParseICalendar(data []byte, windowStart, windowEnd time.Time) (*ParsedCalen
StartTime: base.startTime,
EndTime: base.endTime,
Cancelled: base.cancelled,
Busy: base.busy,
})
}
}
Expand Down
61 changes: 61 additions & 0 deletions internal/calendar/ical_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,64 @@ END:VCALENDAR`
t.Fatalf("timezone: got %q, want %q", tz, "Europe/London")
}
}

func TestParseICalendar_Transparency(t *testing.T) {
icalData := `BEGIN:VCALENDAR
VERSION:2.0
BEGIN:VEVENT
UID:busy-event
DTSTART:20260406T100000Z
DTEND:20260406T110000Z
SUMMARY:Busy Event
TRANSP:OPAQUE
END:VEVENT
BEGIN:VEVENT
UID:free-event
DTSTART:20260406T120000Z
DTEND:20260406T130000Z
SUMMARY:Free Event
TRANSP:TRANSPARENT
END:VEVENT
BEGIN:VEVENT
UID:default-busy-event
DTSTART:20260406T140000Z
DTEND:20260406T150000Z
SUMMARY:Default Busy Event
END:VEVENT
END:VCALENDAR`

now := time.Date(2026, 4, 6, 10, 0, 0, 0, time.UTC)
events, err := parseICalendar([]byte(icalData), now)
if err != nil {
t.Fatalf("parseICalendar: %v", err)
}

if len(events) != 3 {
t.Fatalf("expected 3 events, got %d", len(events))
}

tests := []struct {
summary string
busy bool
}{
{"Busy Event", true},
{"Free Event", false},
{"Default Busy Event", true},
}

for _, tt := range tests {
found := false
for _, ev := range events {
if ev.Summary == tt.summary {
found = true
if ev.Busy != tt.busy {
t.Errorf("%s: Busy got %v, want %v", tt.summary, ev.Busy, tt.busy)
}
break
}
}
if !found {
t.Errorf("could not find event: %s", tt.summary)
}
}
}