@@ -14,6 +14,7 @@ import (
1414 "google.golang.org/api/calendar/v3"
1515 "google.golang.org/api/option"
1616
17+ "github.com/steipete/gogcli/internal/outfmt"
1718 "github.com/steipete/gogcli/internal/ui"
1819)
1920
@@ -139,6 +140,89 @@ func TestCalendarProposeTimeCmd_Text(t *testing.T) {
139140 }
140141}
141142
143+ func TestCalendarProposeTimeCmd_JSON (t * testing.T ) {
144+ origNew := newCalendarService
145+ origOpen := openProposeTimeBrowser
146+ t .Cleanup (func () {
147+ newCalendarService = origNew
148+ openProposeTimeBrowser = origOpen
149+ })
150+ openProposeTimeBrowser = func (url string ) error { return nil }
151+
152+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
153+ path := strings .TrimPrefix (r .URL .Path , "/calendar/v3" )
154+ if strings .Contains (path , "/calendars/cal1/events/evt1" ) && r .Method == http .MethodGet {
155+ w .Header ().Set ("Content-Type" , "application/json" )
156+ _ = json .NewEncoder (w ).Encode (map [string ]any {
157+ "id" : "evt1" ,
158+ "summary" : "Team Meeting" ,
159+ "start" : map [string ]string {"dateTime" : "2026-01-16T19:30:00-08:00" },
160+ "end" : map [string ]string {"dateTime" : "2026-01-16T20:30:00-08:00" },
161+ "attendees" : []map [string ]any {
162+ {"email" : "a@b.com" , "self" : true },
163+ {"email" : "organizer@b.com" , "organizer" : true },
164+ },
165+ })
166+ return
167+ }
168+ http .NotFound (w , r )
169+ }))
170+ defer srv .Close ()
171+
172+ svc , err := calendar .NewService (context .Background (),
173+ option .WithoutAuthentication (),
174+ option .WithHTTPClient (srv .Client ()),
175+ option .WithEndpoint (srv .URL + "/" ),
176+ )
177+ if err != nil {
178+ t .Fatalf ("NewService: %v" , err )
179+ }
180+ newCalendarService = func (context.Context , string ) (* calendar.Service , error ) { return svc , nil }
181+
182+ flags := & RootFlags {Account : "a@b.com" , JSON : true }
183+ out := captureStdout (t , func () {
184+ u , uiErr := ui .New (ui.Options {Stdout : os .Stdout , Stderr : io .Discard , Color : "never" })
185+ if uiErr != nil {
186+ t .Fatalf ("ui.New: %v" , uiErr )
187+ }
188+ ctx := ui .WithUI (context .Background (), u )
189+ ctx = outfmt .WithMode (ctx , outfmt.Mode {JSON : true })
190+
191+ cmd := & CalendarProposeTimeCmd {}
192+ if err := runKong (t , cmd , []string {"cal1" , "evt1" }, ctx , flags ); err != nil {
193+ t .Fatalf ("propose-time JSON: %v" , err )
194+ }
195+ })
196+
197+ // Parse and verify JSON structure
198+ var result map [string ]any
199+ if err := json .Unmarshal ([]byte (out ), & result ); err != nil {
200+ t .Fatalf ("failed to parse JSON output: %v\n output: %s" , err , out )
201+ }
202+
203+ // Verify required fields
204+ requiredFields := []string {"event_id" , "calendar_id" , "summary" , "propose_url" , "api_limitation" , "issue_tracker_url" , "upvote_action" , "current_start" , "current_end" }
205+ for _ , field := range requiredFields {
206+ if _ , ok := result [field ]; ! ok {
207+ t .Errorf ("JSON missing required field %q" , field )
208+ }
209+ }
210+
211+ if result ["event_id" ] != "evt1" {
212+ t .Errorf ("event_id = %v, want evt1" , result ["event_id" ])
213+ }
214+ if result ["calendar_id" ] != "cal1" {
215+ t .Errorf ("calendar_id = %v, want cal1" , result ["calendar_id" ])
216+ }
217+ if result ["summary" ] != "Team Meeting" {
218+ t .Errorf ("summary = %v, want Team Meeting" , result ["summary" ])
219+ }
220+ proposeURL , ok := result ["propose_url" ].(string )
221+ if ! ok || ! strings .Contains (proposeURL , "proposetime/" ) {
222+ t .Errorf ("propose_url invalid: %v" , result ["propose_url" ])
223+ }
224+ }
225+
142226func TestCalendarProposeTimeCmd_WithDecline (t * testing.T ) {
143227 origNew := newCalendarService
144228 origOpen := openProposeTimeBrowser
0 commit comments