github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/python/license_test.go (about)

     1  package python
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  func TestFormatPyPiRegistryURL(t *testing.T) {
    15  	tests := []struct {
    16  		name          string
    17  		version       string
    18  		expected      string
    19  		expectedError error
    20  	}{
    21  		{
    22  			name:          "package1",
    23  			version:       "1.0",
    24  			expected:      "https://pypi.org/pypi/package1/1.0/json",
    25  			expectedError: nil,
    26  		},
    27  		{
    28  			name:          "package-1",
    29  			version:       "",
    30  			expected:      "https://pypi.org/pypi/package-1/json",
    31  			expectedError: nil,
    32  		},
    33  		{
    34  			name:          "_",
    35  			version:       "a",
    36  			expected:      "https://pypi.org/pypi/_/a/json",
    37  			expectedError: nil,
    38  		},
    39  		{
    40  			name:          "",
    41  			version:       "a",
    42  			expected:      "",
    43  			expectedError: fmt.Errorf("unable to format pypi request for a blank package name"),
    44  		},
    45  	}
    46  
    47  	cfg := DefaultCatalogerConfig()
    48  
    49  	for _, test := range tests {
    50  		t.Run(test.name, func(t *testing.T) {
    51  			got, err := formatPypiRegistryURL(cfg.PypiBaseURL, test.name, test.version)
    52  
    53  			require.Equal(t, test.expected, got)
    54  			if test.expectedError != nil {
    55  				require.ErrorContains(t, err, test.expectedError.Error())
    56  			} else {
    57  				require.NoError(t, err)
    58  			}
    59  		})
    60  	}
    61  
    62  }
    63  
    64  func TestGetLicenseFromPypiRegistry(t *testing.T) {
    65  	mux, url, teardown := setupPypiRegistry()
    66  	defer teardown()
    67  
    68  	tests := []struct {
    69  		name            string
    70  		version         string
    71  		requestHandlers []handlerPath
    72  		expected        string
    73  		expectedError   error
    74  	}{
    75  		{
    76  			name:    "certifi",
    77  			version: "2025.10.5",
    78  			requestHandlers: []handlerPath{
    79  				{
    80  					path:    "/certifi/2025.10.5/json",
    81  					handler: generateMockPypiRegistryHandler("test-fixtures/pypi-remote/registry_response.json"),
    82  				},
    83  			},
    84  			expected: "MPL-2.0",
    85  		},
    86  		{
    87  			name:    "package",
    88  			version: "1.0",
    89  			requestHandlers: []handlerPath{
    90  				{
    91  					path:    "/package/1.0/json",
    92  					handler: generateMockPypiRegistryHandlerWithStatus("", http.StatusNotFound),
    93  				},
    94  			},
    95  			expected:      "",
    96  			expectedError: fmt.Errorf("unable to get package from pypi registry"),
    97  		},
    98  		{
    99  			name:    "package",
   100  			version: "2.0",
   101  			requestHandlers: []handlerPath{
   102  				{
   103  					path:    "/package/2.0/json",
   104  					handler: generateMockPypiRegistryHandler("test-fixtures/pypi-remote/registry_response_bad.json"),
   105  				},
   106  			},
   107  			expected:      "",
   108  			expectedError: fmt.Errorf("unable to parse license from pypi registry: EOF"),
   109  		},
   110  	}
   111  	for _, tc := range tests {
   112  		t.Run(tc.name, func(t *testing.T) {
   113  			// set up the mock server
   114  			for _, handler := range tc.requestHandlers {
   115  				mux.HandleFunc(handler.path, handler.handler)
   116  			}
   117  			got, err := getLicenseFromPypiRegistry(url, tc.name, tc.version)
   118  			require.Equal(t, tc.expected, got)
   119  			if tc.expectedError != nil {
   120  				require.ErrorContains(t, err, tc.expectedError.Error())
   121  			} else {
   122  				require.NoError(t, err)
   123  			}
   124  		})
   125  	}
   126  }
   127  
   128  type handlerPath struct {
   129  	path    string
   130  	handler func(w http.ResponseWriter, r *http.Request)
   131  }
   132  
   133  func generateMockPypiRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
   134  	return generateMockPypiRegistryHandlerWithStatus(responseFixture, http.StatusOK)
   135  }
   136  
   137  func generateMockPypiRegistryHandlerWithStatus(responseFixture string, mockHttpStatus int) func(w http.ResponseWriter, r *http.Request) {
   138  	return func(w http.ResponseWriter, r *http.Request) {
   139  		if mockHttpStatus != http.StatusOK {
   140  			http.Error(w, fmt.Errorf("Error for status").Error(), http.StatusNotFound)
   141  			return
   142  		}
   143  
   144  		w.WriteHeader(http.StatusOK)
   145  		// Copy the file's content to the response writer
   146  		file, err := os.Open(responseFixture)
   147  		if err != nil {
   148  			http.Error(w, err.Error(), http.StatusInternalServerError)
   149  			return
   150  		}
   151  		defer file.Close()
   152  
   153  		_, err = io.Copy(w, file)
   154  		if err != nil {
   155  			http.Error(w, err.Error(), http.StatusInternalServerError)
   156  			return
   157  		}
   158  	}
   159  }
   160  
   161  // setup sets up a test HTTP server for mocking requests to a particular registry.
   162  // The returned url is injected into the Config so the client uses the test server.
   163  // Tests should register handlers on mux to simulate the expected request/response structure
   164  func setupPypiRegistry() (mux *http.ServeMux, serverURL string, teardown func()) {
   165  	// mux is the HTTP request multiplexer used with the test server.
   166  	mux = http.NewServeMux()
   167  
   168  	// We want to ensure that tests catch mistakes where the endpoint URL is
   169  	// specified as absolute rather than relative. It only makes a difference
   170  	// when there's a non-empty base URL path. So, use that. See issue #752.
   171  	apiHandler := http.NewServeMux()
   172  	apiHandler.Handle("/", mux)
   173  	// server is a test HTTP server used to provide mock API responses.
   174  	server := httptest.NewServer(apiHandler)
   175  
   176  	return mux, server.URL, server.Close
   177  }