@@ -143,6 +143,66 @@ func TestGmailLabelsGetCmd_JSON(t *testing.T) {
143143 }
144144}
145145
146+ func TestGmailLabelsGetCmd_ExactIDBeatsCaseFoldedName (t * testing.T ) {
147+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
148+ switch {
149+ case strings .HasSuffix (r .URL .Path , "/users/me/labels" ) || strings .HasSuffix (r .URL .Path , "/gmail/v1/users/me/labels" ):
150+ w .Header ().Set ("Content-Type" , "application/json" )
151+ _ = json .NewEncoder (w ).Encode (map [string ]any {
152+ "labels" : []map [string ]any {
153+ {"id" : "Label_9" , "name" : "Original" , "type" : "user" },
154+ {"id" : "Label_10" , "name" : "label_9" , "type" : "user" },
155+ },
156+ })
157+ return
158+ case strings .Contains (r .URL .Path , "/users/me/labels/" ) || strings .Contains (r .URL .Path , "/gmail/v1/users/me/labels/" ):
159+ id := r .URL .Path [strings .LastIndex (r .URL .Path , "/" )+ 1 :]
160+ if id != "Label_9" {
161+ http .NotFound (w , r )
162+ return
163+ }
164+ w .Header ().Set ("Content-Type" , "application/json" )
165+ _ = json .NewEncoder (w ).Encode (map [string ]any {
166+ "id" : "Label_9" ,
167+ "name" : "Original" ,
168+ "type" : "user" ,
169+ })
170+ return
171+ default :
172+ http .NotFound (w , r )
173+ return
174+ }
175+ }))
176+ defer srv .Close ()
177+ stubGmailService (t , srv )
178+
179+ out := captureStdout (t , func () {
180+ u , uiErr := ui .New (ui.Options {Stdout : io .Discard , Stderr : io .Discard , Color : "never" })
181+ if uiErr != nil {
182+ t .Fatalf ("ui.New: %v" , uiErr )
183+ }
184+ ctx := ui .WithUI (context .Background (), u )
185+ ctx = outfmt .WithMode (ctx , outfmt.Mode {JSON : true })
186+
187+ cmd := & GmailLabelsGetCmd {}
188+ if err := runKong (
t ,
cmd , []
string {
"Label_9" },
ctx ,
& RootFlags {
Account :
"[email protected] " });
err != nil {
189+ t .Fatalf ("execute: %v" , err )
190+ }
191+ })
192+
193+ var parsed struct {
194+ Label struct {
195+ ID string `json:"id"`
196+ } `json:"label"`
197+ }
198+ if err := json .Unmarshal ([]byte (out ), & parsed ); err != nil {
199+ t .Fatalf ("json parse: %v\n out=%q" , err , out )
200+ }
201+ if parsed .Label .ID != "Label_9" {
202+ t .Fatalf ("exact ID was shadowed: %#v" , parsed .Label )
203+ }
204+ }
205+
146206func TestGmailLabelsListCmd_TextAndJSON (t * testing.T ) {
147207 origNew := newGmailService
148208 t .Cleanup (func () { newGmailService = origNew })
@@ -487,6 +547,47 @@ func TestGmailLabelsCreateCmd_DuplicateName_Preflight(t *testing.T) {
487547 }
488548}
489549
550+ func TestEnsureLabelNameAvailable_DoesNotCaseFoldIDs (t * testing.T ) {
551+ srv := newLabelsServer (t , []map [string ]any {
552+ {"id" : "Label_9" , "name" : "Different Name" , "type" : "user" },
553+ }, nil )
554+ defer srv .Close ()
555+
556+ svc , err := gmail .NewService (context .Background (),
557+ option .WithoutAuthentication (),
558+ option .WithHTTPClient (srv .Client ()),
559+ option .WithEndpoint (srv .URL + "/" ),
560+ )
561+ if err != nil {
562+ t .Fatalf ("NewService: %v" , err )
563+ }
564+
565+ if err := ensureLabelNameAvailable (svc , "label_9" ); err != nil {
566+ t .Fatalf ("label ID should not collide with name: %v" , err )
567+ }
568+ }
569+
570+ func TestEnsureLabelNameAvailable_BlocksExactIDCollision (t * testing.T ) {
571+ srv := newLabelsServer (t , []map [string ]any {
572+ {"id" : "Label_9" , "name" : "Different Name" , "type" : "user" },
573+ }, nil )
574+ defer srv .Close ()
575+
576+ svc , err := gmail .NewService (context .Background (),
577+ option .WithoutAuthentication (),
578+ option .WithHTTPClient (srv .Client ()),
579+ option .WithEndpoint (srv .URL + "/" ),
580+ )
581+ if err != nil {
582+ t .Fatalf ("NewService: %v" , err )
583+ }
584+
585+ err = ensureLabelNameAvailable (svc , "Label_9" )
586+ if err == nil || ! strings .Contains (err .Error (), "label already exists" ) {
587+ t .Fatalf ("expected exact ID collision error, got: %v" , err )
588+ }
589+ }
590+
490591func TestGmailLabelsCreateCmd_DuplicateName_APIError (t * testing.T ) {
491592 srv := newLabelsServer (t , []map [string ]any {}, func (w http.ResponseWriter , r * http.Request ) {
492593 w .Header ().Set ("Content-Type" , "application/json" )
@@ -697,3 +798,42 @@ func TestFetchLabelIDToName(t *testing.T) {
697798 t .Fatalf ("unexpected label2: %q" , m ["Label_2" ])
698799 }
699800}
801+
802+ func TestFetchLabelNameToID_DoesNotCaseFoldIDs (t * testing.T ) {
803+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
804+ if ! (strings .HasSuffix (r .URL .Path , "/users/me/labels" ) || strings .HasSuffix (r .URL .Path , "/gmail/v1/users/me/labels" )) {
805+ http .NotFound (w , r )
806+ return
807+ }
808+ w .Header ().Set ("Content-Type" , "application/json" )
809+ _ = json .NewEncoder (w ).Encode (map [string ]any {
810+ "labels" : []map [string ]any {
811+ {"id" : "Label_1" , "name" : "Custom" , "type" : "user" },
812+ },
813+ })
814+ }))
815+ defer srv .Close ()
816+
817+ svc , err := gmail .NewService (context .Background (),
818+ option .WithoutAuthentication (),
819+ option .WithHTTPClient (srv .Client ()),
820+ option .WithEndpoint (srv .URL + "/" ),
821+ )
822+ if err != nil {
823+ t .Fatalf ("NewService: %v" , err )
824+ }
825+
826+ m , err := fetchLabelNameToID (svc )
827+ if err != nil {
828+ t .Fatalf ("fetchLabelNameToID: %v" , err )
829+ }
830+ if m ["custom" ] != "Label_1" {
831+ t .Fatalf ("missing case-folded name lookup: %#v" , m )
832+ }
833+ if m ["Label_1" ] != "Label_1" {
834+ t .Fatalf ("missing exact ID lookup: %#v" , m )
835+ }
836+ if _ , ok := m ["label_1" ]; ok {
837+ t .Fatalf ("case-folded label ID should not resolve: %#v" , m )
838+ }
839+ }
0 commit comments