@@ -3,10 +3,12 @@ package cmd
33import (
44 "context"
55 "encoding/json"
6+ "errors"
67 "io"
78 "net/http"
89 "net/http/httptest"
910 "strings"
11+ "sync"
1012 "testing"
1113
1214 "google.golang.org/api/calendar/v3"
@@ -115,3 +117,246 @@ func TestCalendarEventsCmd_DefaultsToPrimary(t *testing.T) {
115117 t .Fatalf ("unexpected output: %q" , out )
116118 }
117119}
120+
121+ func TestCalendarEventsCmd_CalendarsFlag (t * testing.T ) {
122+ origNew := newCalendarService
123+ t .Cleanup (func () { newCalendarService = origNew })
124+
125+ var mu sync.Mutex
126+ calls := make (map [string ]int )
127+
128+ srv := httptest .NewServer (withPrimaryCalendar (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
129+ switch {
130+ case strings .Contains (r .URL .Path , "/calendarList" ) &&
131+ ! strings .Contains (r .URL .Path , "/calendarList/primary" ) &&
132+ r .Method == http .MethodGet :
133+ w .Header ().Set ("Content-Type" , "application/json" )
134+ _ = json .NewEncoder (w ).Encode (map [string ]any {
135+ "items" : []map [string ]any {
136+ {"id" : "c1" , "summary" : "Work" },
137+ {"id" : "c2" , "summary" : "Family" },
138+ {"id" : "c3" , "summary" : "Other" },
139+ },
140+ })
141+ return
142+ case strings .Contains (r .URL .Path , "/calendars/c1/events" ) && r .Method == http .MethodGet :
143+ mu .Lock ()
144+ calls ["c1" ]++
145+ mu .Unlock ()
146+ w .Header ().Set ("Content-Type" , "application/json" )
147+ _ = json .NewEncoder (w ).Encode (map [string ]any {
148+ "items" : []map [string ]any {
149+ {"id" : "e1" , "summary" : "Event 1" },
150+ },
151+ })
152+ return
153+ case strings .Contains (r .URL .Path , "/calendars/c2/events" ) && r .Method == http .MethodGet :
154+ mu .Lock ()
155+ calls ["c2" ]++
156+ mu .Unlock ()
157+ w .Header ().Set ("Content-Type" , "application/json" )
158+ _ = json .NewEncoder (w ).Encode (map [string ]any {
159+ "items" : []map [string ]any {
160+ {"id" : "e2" , "summary" : "Event 2" },
161+ },
162+ })
163+ return
164+ case strings .Contains (r .URL .Path , "/calendars/c3/events" ) && r .Method == http .MethodGet :
165+ mu .Lock ()
166+ calls ["c3" ]++
167+ mu .Unlock ()
168+ http .Error (w , "unexpected calendar" , http .StatusBadRequest )
169+ return
170+ default :
171+ http .NotFound (w , r )
172+ return
173+ }
174+ })))
175+ defer srv .Close ()
176+
177+ svc , err := calendar .NewService (context .Background (),
178+ option .WithoutAuthentication (),
179+ option .WithHTTPClient (srv .Client ()),
180+ option .WithEndpoint (srv .URL + "/" ),
181+ )
182+ if err != nil {
183+ t .Fatalf ("NewService: %v" , err )
184+ }
185+ newCalendarService = func (context.Context , string ) (* calendar.Service , error ) { return svc , nil }
186+
187+ u , err := ui .New (ui.Options {Stdout : io .Discard , Stderr : io .Discard , Color : "never" })
188+ if err != nil {
189+ t .Fatalf ("ui.New: %v" , err )
190+ }
191+ ctx := outfmt .WithMode (ui .WithUI (context .Background (), u ), outfmt.Mode {JSON : true })
192+ flags := & RootFlags {Account : "a@b.com" }
193+
194+ cmd := & CalendarEventsCmd {
195+ Calendars : "1,Family" ,
196+ From : "2025-01-01T00:00:00Z" ,
197+ To : "2025-01-02T00:00:00Z" ,
198+ }
199+ out := captureStdout (t , func () {
200+ if err := cmd .Run (ctx , flags ); err != nil {
201+ t .Fatalf ("Run: %v" , err )
202+ }
203+ })
204+
205+ var parsed struct {
206+ Events []map [string ]any `json:"events"`
207+ }
208+ if err := json .Unmarshal ([]byte (out ), & parsed ); err != nil {
209+ t .Fatalf ("json parse: %v" , err )
210+ }
211+ if len (parsed .Events ) != 2 {
212+ t .Fatalf ("unexpected events: %#v" , parsed .Events )
213+ }
214+
215+ mu .Lock ()
216+ defer mu .Unlock ()
217+ if calls ["c1" ] == 0 || calls ["c2" ] == 0 || calls ["c3" ] != 0 {
218+ t .Fatalf ("unexpected calendar calls: %#v" , calls )
219+ }
220+ }
221+
222+ func TestResolveCalendarIDs_IndexOutOfRange (t * testing.T ) {
223+ srv := httptest .NewServer (withPrimaryCalendar (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
224+ if strings .Contains (r .URL .Path , "/calendarList" ) &&
225+ ! strings .Contains (r .URL .Path , "/calendarList/primary" ) &&
226+ r .Method == http .MethodGet {
227+ w .Header ().Set ("Content-Type" , "application/json" )
228+ _ = json .NewEncoder (w ).Encode (map [string ]any {
229+ "items" : []map [string ]any {
230+ {"id" : "c1" , "summary" : "Work" },
231+ },
232+ })
233+ return
234+ }
235+ http .NotFound (w , r )
236+ })))
237+ defer srv .Close ()
238+
239+ svc , err := calendar .NewService (context .Background (),
240+ option .WithoutAuthentication (),
241+ option .WithHTTPClient (srv .Client ()),
242+ option .WithEndpoint (srv .URL + "/" ),
243+ )
244+ if err != nil {
245+ t .Fatalf ("NewService: %v" , err )
246+ }
247+
248+ _ , err = resolveCalendarIDs (context .Background (), svc , []string {"2" })
249+ if err == nil {
250+ t .Fatalf ("expected error" )
251+ }
252+ var ee * ExitError
253+ if ! errors .As (err , & ee ) || ee .Code != 2 {
254+ t .Fatalf ("expected usage error, got %v" , err )
255+ }
256+ }
257+
258+ func TestResolveCalendarIDs_AmbiguousName (t * testing.T ) {
259+ srv := httptest .NewServer (withPrimaryCalendar (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
260+ if strings .Contains (r .URL .Path , "/calendarList" ) &&
261+ ! strings .Contains (r .URL .Path , "/calendarList/primary" ) &&
262+ r .Method == http .MethodGet {
263+ w .Header ().Set ("Content-Type" , "application/json" )
264+ _ = json .NewEncoder (w ).Encode (map [string ]any {
265+ "items" : []map [string ]any {
266+ {"id" : "c1" , "summary" : "Work" },
267+ {"id" : "c2" , "summary" : "Work" },
268+ {"id" : "c3" , "summary" : "Family" },
269+ },
270+ })
271+ return
272+ }
273+ http .NotFound (w , r )
274+ })))
275+ defer srv .Close ()
276+
277+ svc , err := calendar .NewService (context .Background (),
278+ option .WithoutAuthentication (),
279+ option .WithHTTPClient (srv .Client ()),
280+ option .WithEndpoint (srv .URL + "/" ),
281+ )
282+ if err != nil {
283+ t .Fatalf ("NewService: %v" , err )
284+ }
285+
286+ _ , err = resolveCalendarIDs (context .Background (), svc , []string {"Work" })
287+ if err == nil {
288+ t .Fatalf ("expected error" )
289+ }
290+ var ee * ExitError
291+ if ! errors .As (err , & ee ) || ee .Code != 2 {
292+ t .Fatalf ("expected usage error, got %v" , err )
293+ }
294+ if ! strings .Contains (err .Error (), "ambiguous" ) {
295+ t .Fatalf ("expected ambiguous error, got %v" , err )
296+ }
297+ }
298+
299+ func TestResolveCalendarIDs_UnrecognizedName (t * testing.T ) {
300+ srv := httptest .NewServer (withPrimaryCalendar (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
301+ if strings .Contains (r .URL .Path , "/calendarList" ) &&
302+ ! strings .Contains (r .URL .Path , "/calendarList/primary" ) &&
303+ r .Method == http .MethodGet {
304+ w .Header ().Set ("Content-Type" , "application/json" )
305+ _ = json .NewEncoder (w ).Encode (map [string ]any {
306+ "items" : []map [string ]any {
307+ {"id" : "c1" , "summary" : "Work" },
308+ {"id" : "c2" , "summary" : "Family" },
309+ },
310+ })
311+ return
312+ }
313+ http .NotFound (w , r )
314+ })))
315+ defer srv .Close ()
316+
317+ svc , err := calendar .NewService (context .Background (),
318+ option .WithoutAuthentication (),
319+ option .WithHTTPClient (srv .Client ()),
320+ option .WithEndpoint (srv .URL + "/" ),
321+ )
322+ if err != nil {
323+ t .Fatalf ("NewService: %v" , err )
324+ }
325+
326+ // Test single unrecognized name
327+ _ , err = resolveCalendarIDs (context .Background (), svc , []string {"NonExistent" })
328+ if err == nil {
329+ t .Fatalf ("expected error for unrecognized calendar name" )
330+ }
331+ var ee * ExitError
332+ if ! errors .As (err , & ee ) || ee .Code != 2 {
333+ t .Fatalf ("expected usage error, got %v" , err )
334+ }
335+ if ! strings .Contains (err .Error (), "unrecognized calendar name(s)" ) {
336+ t .Fatalf ("expected error message to mention unrecognized calendar, got: %v" , err )
337+ }
338+ if ! strings .Contains (err .Error (), "NonExistent" ) {
339+ t .Fatalf ("expected error message to include the unrecognized name, got: %v" , err )
340+ }
341+
342+ // Test multiple unrecognized names
343+ _ , err = resolveCalendarIDs (context .Background (), svc , []string {"Work" , "Unknown1" , "Unknown2" })
344+ if err == nil {
345+ t .Fatalf ("expected error for unrecognized calendar names" )
346+ }
347+ if ! errors .As (err , & ee ) || ee .Code != 2 {
348+ t .Fatalf ("expected usage error, got %v" , err )
349+ }
350+ if ! strings .Contains (err .Error (), "Unknown1" ) || ! strings .Contains (err .Error (), "Unknown2" ) {
351+ t .Fatalf ("expected error message to include all unrecognized names, got: %v" , err )
352+ }
353+
354+ // Test valid names still work
355+ ids , err := resolveCalendarIDs (context .Background (), svc , []string {"Work" , "Family" })
356+ if err != nil {
357+ t .Fatalf ("unexpected error for valid calendar names: %v" , err )
358+ }
359+ if len (ids ) != 2 || ids [0 ] != "c1" || ids [1 ] != "c2" {
360+ t .Fatalf ("unexpected ids: %v" , ids )
361+ }
362+ }
0 commit comments