@@ -17,6 +17,7 @@ import (
1717 "github.com/google/go-cmp/cmp"
1818 "github.com/jarcoal/httpmock"
1919 "github.com/linode/linodego/internal/testutil"
20+ "github.com/stretchr/testify/require"
2021)
2122
2223func TestClient_SetAPIVersion (t * testing.T ) {
@@ -703,3 +704,101 @@ func TestMonitorClient_SetAPIBasics(t *testing.T) {
703704 t .Fatal (cmp .Diff (client .resty .BaseURL , expectedHost ))
704705 }
705706}
707+
708+ func TestRedactHeaders (t * testing.T ) {
709+ tests := []struct {
710+ name string
711+ headers http.Header
712+ wantVal map [string ]string
713+ }{
714+ {
715+ name : "redacts authorization header" ,
716+ headers : http.Header {
717+ "Authorization" : []string {"Bearer supersecrettoken" },
718+ "Content-Type" : []string {"application/json" },
719+ },
720+ wantVal : map [string ]string {
721+ "Authorization" : redactHeadersMap ["Authorization" ],
722+ "Content-Type" : "application/json" ,
723+ },
724+ },
725+ {
726+ name : "leaves non-sensitive headers unchanged" ,
727+ headers : http.Header {
728+ "Content-Type" : []string {"application/json" },
729+ "Accept" : []string {"application/json" },
730+ },
731+ wantVal : map [string ]string {
732+ "Content-Type" : "application/json" ,
733+ "Accept" : "application/json" ,
734+ },
735+ },
736+ {
737+ name : "handles empty headers" ,
738+ headers : http.Header {},
739+ wantVal : map [string ]string {},
740+ },
741+ {
742+ name : "does not mutate original headers" ,
743+ headers : http.Header {
744+ "Authorization" : []string {"Bearer supersecrettoken" },
745+ },
746+ wantVal : map [string ]string {
747+ "Authorization" : redactHeadersMap ["Authorization" ],
748+ },
749+ },
750+ }
751+
752+ for _ , tt := range tests {
753+ t .Run (tt .name , func (t * testing.T ) {
754+ originalAuth := tt .headers .Get ("Authorization" )
755+
756+ result := redactHeaders (tt .headers )
757+
758+ // Verify expected values in result
759+ for key , expectedVal := range tt .wantVal {
760+ if got := result .Get (key ); got != expectedVal {
761+ t .Errorf ("redactHeaders() header %q = %q, want %q" , key , got , expectedVal )
762+ }
763+ }
764+
765+ // Verify original was not mutated
766+ if tt .headers .Get ("Authorization" ) != originalAuth {
767+ t .Error ("redactHeaders() mutated the original headers" )
768+ }
769+ })
770+ }
771+ }
772+
773+ func TestEnableLogSanitization (t * testing.T ) {
774+ mockClient := testutil .CreateMockClient (t , NewClient )
775+ mockClient .SetDebug (true )
776+
777+ plainTextToken := "supersecrettoken"
778+ mockClient .SetToken (plainTextToken )
779+
780+ var logBuf bytes.Buffer
781+ logger := testutil .CreateLogger ()
782+ logger .L .SetOutput (& logBuf )
783+ mockClient .SetLogger (logger )
784+
785+ httpmock .RegisterResponder ("GET" , "=~.*" ,
786+ httpmock .NewStringResponder (200 , `{}` ).HeaderSet (http.Header {
787+ "Authorization" : []string {"Bearer " + plainTextToken },
788+ }))
789+
790+ _ , err := mockClient .resty .R ().Get ("https://api.linode.com/v4/test" )
791+ require .NoError (t , err )
792+
793+ logOutput := logBuf .String ()
794+
795+ // Verify token is not present in either request or response logs
796+ if strings .Contains (logOutput , plainTextToken ) {
797+ t .Errorf ("log output contains raw token %q, expected it to be redacted" , plainTextToken )
798+ }
799+
800+ // Verify Authorization header still appears (as redacted value) in request log
801+ if ! strings .Contains (logOutput , "Authorization" ) {
802+ t .Error ("expected Authorization header to appear in request log output" )
803+ }
804+ }
0 commit comments