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 }