github.com/google/osv-scalibr@v0.4.1/veles/secrets/slacktoken/validator_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package slacktoken_test
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"net/http/httptest"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/osv-scalibr/veles"
    26  	"github.com/google/osv-scalibr/veles/secrets/slacktoken"
    27  )
    28  
    29  // mockSlackServer creates a mock Slack API server for testing
    30  func mockSlackServer(t *testing.T, expectedKey string, responseBody string, expectedEndpoint string) *httptest.Server {
    31  	t.Helper()
    32  
    33  	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    34  		// Check if it's a POST request to the expected endpoint
    35  		if r.Method != http.MethodPost {
    36  			t.Errorf("unexpected request method: got %s, expected POST", r.Method)
    37  			http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
    38  			return
    39  		}
    40  		if r.URL.Path != expectedEndpoint {
    41  			t.Errorf("unexpected request path: got %s, expected %s", r.URL.Path, expectedEndpoint)
    42  			http.Error(w, "not found", http.StatusNotFound)
    43  			return
    44  		}
    45  
    46  		// Check Authorization header for auth.test endpoint
    47  		if expectedEndpoint == "/api/auth.test" {
    48  			authHeader := r.Header.Get("Authorization")
    49  			if !strings.Contains(authHeader, expectedKey) {
    50  				w.Header().Set("Content-Type", "application/json")
    51  				w.WriteHeader(http.StatusOK)
    52  				_, _ = w.Write([]byte(`{"ok":false,"error":"invalid_auth"}`))
    53  				return
    54  			}
    55  		}
    56  		// Set response
    57  		w.Header().Set("Content-Type", "application/json")
    58  		w.WriteHeader(http.StatusOK)
    59  		_, _ = w.Write([]byte(responseBody))
    60  	}))
    61  }
    62  
    63  func TestAppLevelTokenValidator(t *testing.T) {
    64  	cases := []struct {
    65  		name              string
    66  		key               slacktoken.SlackAppLevelToken
    67  		serverExpectedKey string
    68  		responseBody      string
    69  		expectedEndpoint  string
    70  		want              veles.ValidationStatus
    71  		expectError       bool
    72  	}{
    73  		{
    74  			name:              "valid_app_level_token",
    75  			key:               slacktoken.SlackAppLevelToken{Token: testAppLevelToken},
    76  			serverExpectedKey: testAppLevelToken,
    77  			responseBody:      `{"ok":true}`,
    78  			expectedEndpoint:  "/api/auth.test",
    79  			want:              veles.ValidationValid,
    80  		},
    81  		{
    82  			name:              "invalid_app_level_token",
    83  			key:               slacktoken.SlackAppLevelToken{Token: "random_string"},
    84  			serverExpectedKey: testAppLevelToken,
    85  			responseBody:      `{"ok":false,"error":"invalid_auth"}`,
    86  			expectedEndpoint:  "/api/auth.test",
    87  			want:              veles.ValidationInvalid,
    88  		},
    89  		{
    90  			name:              "server_error_app_level",
    91  			key:               slacktoken.SlackAppLevelToken{Token: testAppLevelToken},
    92  			serverExpectedKey: testAppLevelToken,
    93  			responseBody:      `{"ok":false,"error":"server_error"}`,
    94  			expectedEndpoint:  "/api/auth.test",
    95  			want:              veles.ValidationFailed,
    96  		},
    97  	}
    98  
    99  	for _, tc := range cases {
   100  		t.Run(tc.name, func(t *testing.T) {
   101  			// Create a mock server
   102  			server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint)
   103  			defer server.Close()
   104  
   105  			validator := slacktoken.NewAppLevelTokenValidator()
   106  			validator.HTTPC = server.Client()
   107  			validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   108  
   109  			got, err := validator.Validate(t.Context(), tc.key)
   110  
   111  			// Check error expectation
   112  			if tc.expectError {
   113  				if err == nil {
   114  					t.Errorf("Validate() expected error, got nil")
   115  				}
   116  			} else {
   117  				if err != nil {
   118  					t.Errorf("Validate() unexpected error: %v", err)
   119  				}
   120  			}
   121  
   122  			// Check validation status
   123  			if got != tc.want {
   124  				t.Errorf("Validate() = %v, want %v", got, tc.want)
   125  			}
   126  		})
   127  	}
   128  }
   129  
   130  func TestAppConfigAccessTokenValidator(t *testing.T) {
   131  	cases := []struct {
   132  		name              string
   133  		key               slacktoken.SlackAppConfigAccessToken
   134  		serverExpectedKey string
   135  		responseBody      string
   136  		expectedEndpoint  string
   137  		want              veles.ValidationStatus
   138  		expectError       bool
   139  	}{
   140  		{
   141  			name:              "valid_access_token",
   142  			key:               slacktoken.SlackAppConfigAccessToken{Token: testAppConfigAccessToken},
   143  			serverExpectedKey: testAppConfigAccessToken,
   144  			responseBody:      `{"ok":true}`,
   145  			expectedEndpoint:  "/api/auth.test",
   146  			want:              veles.ValidationValid,
   147  		},
   148  		{
   149  			name:              "invalid_access_token",
   150  			key:               slacktoken.SlackAppConfigAccessToken{Token: "invalid_access_token"},
   151  			serverExpectedKey: testAppConfigAccessToken,
   152  			responseBody:      `{"ok":false,"error":"invalid_auth"}`,
   153  			expectedEndpoint:  "/api/auth.test",
   154  			want:              veles.ValidationInvalid,
   155  		},
   156  	}
   157  
   158  	for _, tc := range cases {
   159  		t.Run(tc.name, func(t *testing.T) {
   160  			// Create a mock server
   161  			server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint)
   162  			defer server.Close()
   163  
   164  			validator := slacktoken.NewAppConfigAccessTokenValidator()
   165  			validator.HTTPC = server.Client()
   166  			validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   167  
   168  			got, err := validator.Validate(t.Context(), tc.key)
   169  
   170  			// Check error expectation
   171  			if tc.expectError {
   172  				if err == nil {
   173  					t.Errorf("Validate() expected error, got nil")
   174  				}
   175  			} else {
   176  				if err != nil {
   177  					t.Errorf("Validate() unexpected error: %v", err)
   178  				}
   179  			}
   180  
   181  			// Check validation status
   182  			if got != tc.want {
   183  				t.Errorf("Validate() = %v, want %v", got, tc.want)
   184  			}
   185  		})
   186  	}
   187  }
   188  
   189  func TestAppConfigRefreshTokenValidator(t *testing.T) {
   190  	cases := []struct {
   191  		name              string
   192  		key               slacktoken.SlackAppConfigRefreshToken
   193  		serverExpectedKey string
   194  		responseBody      string
   195  		expectedEndpoint  string
   196  		want              veles.ValidationStatus
   197  		expectError       bool
   198  	}{
   199  		{
   200  			name:              "valid_refresh_token",
   201  			key:               slacktoken.SlackAppConfigRefreshToken{Token: testAppConfigRefreshToken},
   202  			serverExpectedKey: testAppConfigRefreshToken,
   203  			responseBody:      `{"ok":true}`,
   204  			expectedEndpoint:  "/api/auth.test",
   205  			want:              veles.ValidationValid,
   206  		},
   207  		{
   208  			name:              "invalid_refresh_token",
   209  			key:               slacktoken.SlackAppConfigRefreshToken{Token: "invalid_refresh_token"},
   210  			serverExpectedKey: testAppConfigRefreshToken,
   211  			responseBody:      `{"ok":false,"error":"invalid_auth"}`,
   212  			expectedEndpoint:  "/api/auth.test",
   213  			want:              veles.ValidationInvalid,
   214  		},
   215  	}
   216  
   217  	for _, tc := range cases {
   218  		t.Run(tc.name, func(t *testing.T) {
   219  			// Create a mock server
   220  			server := mockSlackServer(t, tc.serverExpectedKey, tc.responseBody, tc.expectedEndpoint)
   221  			defer server.Close()
   222  
   223  			validator := slacktoken.NewAppConfigRefreshTokenValidator()
   224  			validator.HTTPC = server.Client()
   225  			validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   226  
   227  			got, err := validator.Validate(t.Context(), tc.key)
   228  
   229  			// Check error expectation
   230  			if tc.expectError {
   231  				if err == nil {
   232  					t.Errorf("Validate() expected error, got nil")
   233  				}
   234  			} else {
   235  				if err != nil {
   236  					t.Errorf("Validate() unexpected error: %v", err)
   237  				}
   238  			}
   239  
   240  			// Check validation status
   241  			if got != tc.want {
   242  				t.Errorf("Validate() = %v, want %v", got, tc.want)
   243  			}
   244  		})
   245  	}
   246  }
   247  
   248  func TestValidator_ContextCancellation(t *testing.T) {
   249  	// Create a server that delays response significantly
   250  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   251  		// Sleep longer than the context timeout to trigger cancellation
   252  		time.Sleep(100 * time.Millisecond)
   253  		w.Header().Set("Content-Type", "application/json")
   254  		w.WriteHeader(http.StatusOK)
   255  		_, _ = w.Write([]byte(`{"ok":true"}`))
   256  	}))
   257  	defer server.Close()
   258  
   259  	t.Run("app_level_token", func(t *testing.T) {
   260  		validator := slacktoken.NewAppLevelTokenValidator()
   261  		validator.HTTPC = server.Client()
   262  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   263  		key := slacktoken.SlackAppLevelToken{Token: testAppLevelToken}
   264  
   265  		// Create context with a very short timeout
   266  		ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond)
   267  		defer cancel()
   268  
   269  		// Test validation with cancelled context
   270  		got, err := validator.Validate(ctx, key)
   271  
   272  		if err == nil {
   273  			t.Errorf("Validate() expected error due to context cancellation, got nil")
   274  		}
   275  		if got != veles.ValidationFailed {
   276  			t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed)
   277  		}
   278  	})
   279  
   280  	// Test with App Config Access Token validator
   281  	t.Run("app_config_access_token", func(t *testing.T) {
   282  		validator := slacktoken.NewAppConfigAccessTokenValidator()
   283  		validator.HTTPC = server.Client()
   284  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   285  		key := slacktoken.SlackAppConfigAccessToken{Token: testAppConfigAccessToken}
   286  		ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond)
   287  		defer cancel()
   288  
   289  		// Test validation with cancelled context
   290  		got, err := validator.Validate(ctx, key)
   291  
   292  		if err == nil {
   293  			t.Errorf("Validate() expected error due to context cancellation, got nil")
   294  		}
   295  		if got != veles.ValidationFailed {
   296  			t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed)
   297  		}
   298  	})
   299  
   300  	// Test with App Config Refresh Token validator
   301  	t.Run("app_config_refresh_token", func(t *testing.T) {
   302  		validator := slacktoken.NewAppConfigRefreshTokenValidator()
   303  		validator.HTTPC = server.Client()
   304  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   305  		key := slacktoken.SlackAppConfigRefreshToken{Token: testAppConfigRefreshToken}
   306  
   307  		// Create context with a very short timeout
   308  		ctx, cancel := context.WithTimeout(t.Context(), 10*time.Millisecond)
   309  		defer cancel()
   310  
   311  		// Test validation with cancelled context
   312  		got, err := validator.Validate(ctx, key)
   313  
   314  		if err == nil {
   315  			t.Errorf("Validate() expected error due to context cancellation, got nil")
   316  		}
   317  		if got != veles.ValidationFailed {
   318  			t.Errorf("Validate() = %v, want %v", got, veles.ValidationFailed)
   319  		}
   320  	})
   321  }
   322  
   323  func TestValidator_InvalidRequest(t *testing.T) {
   324  	// Create a mock server that returns invalid auth response
   325  	server := mockSlackServer(t, "any-key", `{"ok":false,"error":"invalid_auth"}`, "/api/auth.test")
   326  	defer server.Close()
   327  
   328  	t.Run("app_level_token", func(t *testing.T) {
   329  		validator := slacktoken.NewAppLevelTokenValidator()
   330  		validator.HTTPC = server.Client()
   331  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   332  		testCases := []struct {
   333  			name     string
   334  			key      slacktoken.SlackAppLevelToken
   335  			expected veles.ValidationStatus
   336  		}{
   337  			{
   338  				name:     "empty_key",
   339  				key:      slacktoken.SlackAppLevelToken{Token: ""},
   340  				expected: veles.ValidationInvalid,
   341  			},
   342  			{
   343  				name:     "invalid_key_format",
   344  				key:      slacktoken.SlackAppLevelToken{Token: "invalid-key-format"},
   345  				expected: veles.ValidationInvalid,
   346  			},
   347  		}
   348  
   349  		for _, tc := range testCases {
   350  			t.Run(tc.name, func(t *testing.T) {
   351  				got, err := validator.Validate(t.Context(), tc.key)
   352  
   353  				if err != nil {
   354  					t.Errorf("Validate() unexpected error for %s: %v", tc.name, err)
   355  				}
   356  				if got != tc.expected {
   357  					t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name)
   358  				}
   359  			})
   360  		}
   361  	})
   362  
   363  	// Test with App Config Access Token validator
   364  	t.Run("app_config_access_token", func(t *testing.T) {
   365  		validator := slacktoken.NewAppConfigAccessTokenValidator()
   366  		validator.HTTPC = server.Client()
   367  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   368  		testCases := []struct {
   369  			name     string
   370  			key      slacktoken.SlackAppConfigAccessToken
   371  			expected veles.ValidationStatus
   372  		}{
   373  			{
   374  				name:     "empty_key",
   375  				key:      slacktoken.SlackAppConfigAccessToken{Token: ""},
   376  				expected: veles.ValidationInvalid,
   377  			},
   378  			{
   379  				name:     "invalid_key_format",
   380  				key:      slacktoken.SlackAppConfigAccessToken{Token: "invalid-key-format"},
   381  				expected: veles.ValidationInvalid,
   382  			},
   383  		}
   384  
   385  		for _, tc := range testCases {
   386  			t.Run(tc.name, func(t *testing.T) {
   387  				got, err := validator.Validate(t.Context(), tc.key)
   388  
   389  				if err != nil {
   390  					t.Errorf("Validate() unexpected error for %s: %v", tc.name, err)
   391  				}
   392  				if got != tc.expected {
   393  					t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name)
   394  				}
   395  			})
   396  		}
   397  	})
   398  
   399  	// Test with App Config Refresh Token validator
   400  	t.Run("app_config_refresh_token", func(t *testing.T) {
   401  		validator := slacktoken.NewAppConfigRefreshTokenValidator()
   402  		validator.HTTPC = server.Client()
   403  		validator.Endpoint = server.URL + slacktoken.SlackAPIEndpoint
   404  		testCases := []struct {
   405  			name     string
   406  			key      slacktoken.SlackAppConfigRefreshToken
   407  			expected veles.ValidationStatus
   408  		}{
   409  			{
   410  				name:     "empty_key",
   411  				key:      slacktoken.SlackAppConfigRefreshToken{Token: ""},
   412  				expected: veles.ValidationInvalid,
   413  			},
   414  			{
   415  				name:     "invalid_key_format",
   416  				key:      slacktoken.SlackAppConfigRefreshToken{Token: "invalid-key-format"},
   417  				expected: veles.ValidationInvalid,
   418  			},
   419  		}
   420  
   421  		for _, tc := range testCases {
   422  			t.Run(tc.name, func(t *testing.T) {
   423  				got, err := validator.Validate(t.Context(), tc.key)
   424  
   425  				if err != nil {
   426  					t.Errorf("Validate() unexpected error for %s: %v", tc.name, err)
   427  				}
   428  				if got != tc.expected {
   429  					t.Errorf("Validate() = %v, want %v for %s", got, tc.expected, tc.name)
   430  				}
   431  			})
   432  		}
   433  	})
   434  }