github.com/google/osv-scalibr@v0.4.1/clients/resolution/pypi_registry_client_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 resolution_test
    16  
    17  import (
    18  	"os"
    19  	"testing"
    20  
    21  	"deps.dev/util/resolve"
    22  	"deps.dev/util/resolve/dep"
    23  	"deps.dev/util/resolve/version"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/osv-scalibr/clients/clienttest"
    26  	"github.com/google/osv-scalibr/clients/resolution"
    27  )
    28  
    29  func TestVersions(t *testing.T) {
    30  	srv := clienttest.NewMockHTTPServer(t)
    31  	srv.SetResponse(t, "/beautifulsoup4/", []byte(`
    32  	{
    33  		"files": [
    34  		  {
    35  			"filename": "beautifulsoup4-0.0.1-py2.4-linux-x86_64.egg",
    36  			"yanked": "yanked"
    37  		  },
    38  		  {
    39  			"filename": "beautifulsoup4-0.0.2.zip",
    40  			"yanked": "yanked"
    41  		  },
    42  		  {
    43  			"core-metadata": false,
    44  			"data-dist-info-metadata": false,
    45  			"filename": "beautifulsoup4-4.0.1.tar.gz",
    46  			"hashes": {
    47  			  "sha256": "dc6bc8e8851a1c590c8cc8f25915180fdcce116e268d1f37fa991d2686ea38de"
    48  			},
    49  			"requires-python": null,
    50  			"size": 51024,
    51  			"upload-time": "2014-01-21T05:35:05.558877Z",
    52  			"url": "https://files.pythonhosted.org/packages/6f/be/99dcf74d947cc1e7abef5d0c4572abcb479c33ef791d94453a8fd7987d8f/beautifulsoup4-4.0.1.tar.gz",
    53  			"yanked": false
    54  		  },
    55  		  {
    56  			"core-metadata": false,
    57  			"data-dist-info-metadata": false,
    58  			"filename": "beautifulsoup4-4.0.2.tar.gz",
    59  			"hashes": {
    60  			  "sha256": "353792f8246a9551b232949fb14dce21d9b6ced9207bf9f4a69a4c4eb46c8127"
    61  			},
    62  			"requires-python": null,
    63  			"size": 51240,
    64  			"upload-time": "2014-01-21T05:35:09.581933Z",
    65  			"url": "https://files.pythonhosted.org/packages/a0/75/db36172ea767dd2f0c9817a99e24f7e9b79c2ce63eb2f8b867284cc60daf/beautifulsoup4-4.0.2.tar.gz",
    66  			"yanked": false
    67  		  },
    68  		  {
    69  			"core-metadata": {
    70  			  "sha256": "524392d64a088e56a4232f50d6edb208dc03105394652acb72c6d5fa64c89f3e"
    71  			},
    72  			"data-dist-info-metadata": {
    73  			  "sha256": "524392d64a088e56a4232f50d6edb208dc03105394652acb72c6d5fa64c89f3e"
    74  			},
    75  			"filename": "beautifulsoup4-4.12.3-py3-none-any.whl",
    76  			"hashes": {
    77  			  "sha256": "b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
    78  			},
    79  			"requires-python": ">=3.6.0",
    80  			"size": 147925,
    81  			"upload-time": "2024-01-17T16:53:12.779164Z",
    82  			"url": "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl",
    83  			"yanked": false
    84  		  },
    85  		  {
    86  			"core-metadata": false,
    87  			"data-dist-info-metadata": false,
    88  			"filename": "beautifulsoup4-4.12.3.tar.gz",
    89  			"hashes": {
    90  			  "sha256": "74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"
    91  			},
    92  			"requires-python": ">=3.6.0",
    93  			"size": 581181,
    94  			"upload-time": "2024-01-17T16:53:17.902970Z",
    95  			"url": "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz",
    96  			"yanked": false
    97  		  },
    98  		  {
    99  			"core-metadata": {
   100  			  "sha256": "d0aa787c2b55e5b0b3aff66f137cf33341c5e781cb87b4dc184cbb25c7ac0ab5"
   101  			},
   102  			"data-dist-info-metadata": {
   103  			  "sha256": "d0aa787c2b55e5b0b3aff66f137cf33341c5e781cb87b4dc184cbb25c7ac0ab5"
   104  			},
   105  			"filename": "beautifulsoup4-4.13.0b2-py3-none-any.whl",
   106  			"hashes": {
   107  			  "sha256": "7e05ad0b6c26108d9990e2235e8a9b4e2c03ead6f391ceb60347f8ebea6b80ba"
   108  			},
   109  			"requires-python": ">=3.6.0",
   110  			"size": 179607,
   111  			"upload-time": "2024-03-20T13:00:33.355932Z",
   112  			"url": "https://files.pythonhosted.org/packages/14/7e/e4313dad823c3a0751c99b9bc0182b1dd19aea164ce7445e9a70429b9e92/beautifulsoup4-4.13.0b2-py3-none-any.whl",
   113  			"yanked": false
   114  		  },
   115  		  {
   116  			"core-metadata": false,
   117  			"data-dist-info-metadata": false,
   118  			"filename": "beautifulsoup4-4.13.0b2.tar.gz",
   119  			"hashes": {
   120  			  "sha256": "c684ddec071aa120819889aa9e8940f85c3f3cdaa08e23b9fa26510387897bd5"
   121  			},
   122  			"requires-python": ">=3.6.0",
   123  			"size": 550258,
   124  			"upload-time": "2024-03-20T13:00:31.245327Z",
   125  			"url": "https://files.pythonhosted.org/packages/81/bd/c97d94e2b96f03d1c50bc9de04130e014eda89322ba604923e0c251eb02e/beautifulsoup4-4.13.0b2.tar.gz",
   126  			"yanked": false
   127  		  },
   128  		  {
   129  			"filename": "beautifulsoup4-4.14.tar.gz",
   130  			"yanked": "yanked"
   131  		  }
   132  		],
   133  		"meta": {
   134  		  "_last-serial": 22406780,
   135  		  "api-version": "1.1"
   136  		},
   137  		"name": "beautifulsoup4",
   138  		"versions": [
   139  		  "0.0.1",
   140  		  "0.0.2",
   141  		  "4.0.1",
   142  		  "4.0.2",
   143  		  "4.12.3",
   144  		  "4.13.0b2",
   145  		  "4.14"
   146  		]
   147    }
   148  	`))
   149  
   150  	pk := resolve.PackageKey{
   151  		System: resolve.PyPI,
   152  		Name:   "beautifulsoup4",
   153  	}
   154  	client := resolution.NewPyPIRegistryClient(srv.URL, "")
   155  	got, err := client.Versions(t.Context(), pk)
   156  	if err != nil {
   157  		t.Fatalf("failed to get versions %v: %v", pk, err)
   158  	}
   159  
   160  	var yanked version.AttrSet
   161  	yanked.SetAttr(version.Blocked, "")
   162  	want := []resolve.Version{
   163  		{
   164  			VersionKey: resolve.VersionKey{
   165  				PackageKey:  pk,
   166  				Version:     "0.0.1",
   167  				VersionType: resolve.Concrete,
   168  			},
   169  			AttrSet: yanked,
   170  		},
   171  		{
   172  			VersionKey: resolve.VersionKey{
   173  				PackageKey:  pk,
   174  				Version:     "0.0.2",
   175  				VersionType: resolve.Concrete,
   176  			},
   177  			AttrSet: yanked,
   178  		},
   179  		{
   180  			VersionKey: resolve.VersionKey{
   181  				PackageKey:  pk,
   182  				Version:     "4.0.1",
   183  				VersionType: resolve.Concrete,
   184  			},
   185  		},
   186  		{
   187  			VersionKey: resolve.VersionKey{
   188  				PackageKey:  pk,
   189  				Version:     "4.0.2",
   190  				VersionType: resolve.Concrete,
   191  			},
   192  		},
   193  		{
   194  			VersionKey: resolve.VersionKey{
   195  				PackageKey:  pk,
   196  				Version:     "4.12.3",
   197  				VersionType: resolve.Concrete,
   198  			},
   199  		},
   200  		{
   201  			VersionKey: resolve.VersionKey{
   202  				PackageKey:  pk,
   203  				Version:     "4.13.0b2",
   204  				VersionType: resolve.Concrete,
   205  			},
   206  		},
   207  		{
   208  			VersionKey: resolve.VersionKey{
   209  				PackageKey:  pk,
   210  				Version:     "4.14",
   211  				VersionType: resolve.Concrete,
   212  			},
   213  			AttrSet: yanked,
   214  		},
   215  	}
   216  	if diff := cmp.Diff(want, got); diff != "" {
   217  		t.Fatalf("Versions(%v) mismatch (-want +got):\n%s", pk, diff)
   218  	}
   219  	for i, v := range got {
   220  		if !v.AttrSet.Equal(want[i].AttrSet) {
   221  			t.Errorf("AttrSet for package %s version %s mismatch", v.Name, v.Version)
   222  		}
   223  	}
   224  }
   225  
   226  func TestRequirements(t *testing.T) {
   227  	srv := clienttest.NewMockHTTPServer(t)
   228  	srv.SetResponse(t, "/beautifulsoup4/", []byte(`
   229  	{
   230  		"files": [
   231  		  {
   232  			"core-metadata": false,
   233  			"data-dist-info-metadata": false,
   234  			"filename": "beautifulsoup4-4.0.1.tar.gz",
   235  			"hashes": {
   236  			  "sha256": "dc6bc8e8851a1c590c8cc8f25915180fdcce116e268d1f37fa991d2686ea38de"
   237  			},
   238  			"requires-python": null,
   239  			"size": 51024,
   240  			"upload-time": "2014-01-21T05:35:05.558877Z",
   241  			"url": "https://files.pythonhosted.org/packages/6f/be/99dcf74d947cc1e7abef5d0c4572abcb479c33ef791d94453a8fd7987d8f/beautifulsoup4-4.0.1.tar.gz",
   242  			"yanked": false
   243  		  },
   244  		  {
   245  			"core-metadata": false,
   246  			"data-dist-info-metadata": false,
   247  			"filename": "beautifulsoup4-4.0.2.tar.gz",
   248  			"hashes": {
   249  			  "sha256": "353792f8246a9551b232949fb14dce21d9b6ced9207bf9f4a69a4c4eb46c8127"
   250  			},
   251  			"requires-python": null,
   252  			"size": 51240,
   253  			"upload-time": "2014-01-21T05:35:09.581933Z",
   254  			"url": "https://files.pythonhosted.org/packages/a0/75/db36172ea767dd2f0c9817a99e24f7e9b79c2ce63eb2f8b867284cc60daf/beautifulsoup4-4.0.2.tar.gz",
   255  			"yanked": false
   256  		  },
   257  		  {
   258  			"core-metadata": false,
   259  			"data-dist-info-metadata": false,
   260  			"filename": "beautifulsoup4-4.12.3.tar.gz",
   261  			"hashes": {
   262  			  "sha256": "74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"
   263  			},
   264  			"requires-python": ">=3.6.0",
   265  			"size": 581181,
   266  			"upload-time": "2024-01-17T16:53:17.902970Z",
   267  			"url": "`+srv.URL+`/beautifulsoup4-4.12.3.tar.gz",
   268  			"yanked": false
   269  		  },
   270  		  {
   271  			"core-metadata": {
   272  			  "sha256": "524392d64a088e56a4232f50d6edb208dc03105394652acb72c6d5fa64c89f3e"
   273  			},
   274  			"data-dist-info-metadata": {
   275  			  "sha256": "524392d64a088e56a4232f50d6edb208dc03105394652acb72c6d5fa64c89f3e"
   276  			},
   277  			"filename": "beautifulsoup4-4.12.3-py3-none-any.whl",
   278  			"hashes": {
   279  			  "sha256": "b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
   280  			},
   281  			"requires-python": ">=3.6.0",
   282  			"size": 147925,
   283  			"upload-time": "2024-01-17T16:53:12.779164Z",
   284  			"url": "`+srv.URL+`/beautifulsoup4-4.12.3-py3-none-any.whl",
   285  			"yanked": false
   286  		  },
   287  		  {
   288  			"core-metadata": {
   289  			  "sha256": "d0aa787c2b55e5b0b3aff66f137cf33341c5e781cb87b4dc184cbb25c7ac0ab5"
   290  			},
   291  			"data-dist-info-metadata": {
   292  			  "sha256": "d0aa787c2b55e5b0b3aff66f137cf33341c5e781cb87b4dc184cbb25c7ac0ab5"
   293  			},
   294  			"filename": "beautifulsoup4-4.13.0b2-py3-none-any.whl",
   295  			"hashes": {
   296  			  "sha256": "7e05ad0b6c26108d9990e2235e8a9b4e2c03ead6f391ceb60347f8ebea6b80ba"
   297  			},
   298  			"requires-python": ">=3.6.0",
   299  			"size": 179607,
   300  			"upload-time": "2024-03-20T13:00:33.355932Z",
   301  			"url": "https://files.pythonhosted.org/packages/14/7e/e4313dad823c3a0751c99b9bc0182b1dd19aea164ce7445e9a70429b9e92/beautifulsoup4-4.13.0b2-py3-none-any.whl",
   302  			"yanked": false
   303  		  },
   304  		  {
   305  			"core-metadata": false,
   306  			"data-dist-info-metadata": false,
   307  			"filename": "beautifulsoup4-4.13.0b2.tar.gz",
   308  			"hashes": {
   309  			  "sha256": "c684ddec071aa120819889aa9e8940f85c3f3cdaa08e23b9fa26510387897bd5"
   310  			},
   311  			"requires-python": ">=3.6.0",
   312  			"size": 550258,
   313  			"upload-time": "2024-03-20T13:00:31.245327Z",
   314  			"url": "https://files.pythonhosted.org/packages/81/bd/c97d94e2b96f03d1c50bc9de04130e014eda89322ba604923e0c251eb02e/beautifulsoup4-4.13.0b2.tar.gz",
   315  			"yanked": false
   316  		  },
   317  		  {
   318  			"filename": "beautifulsoup4-4.14.tar.gz",
   319  			"yanked": "yanked"
   320  		  }
   321  		],
   322  		"meta": {
   323  		  "_last-serial": 22406780,
   324  		  "api-version": "1.1"
   325  		},
   326  		"name": "beautifulsoup4",
   327  		"versions": [
   328  		  "4.0.1",
   329  		  "4.0.2",
   330  		  "4.12.3",
   331  		  "4.13.0b2",
   332  		  "4.14"
   333  		]
   334    }
   335  	`))
   336  
   337  	content, err := os.ReadFile("testdata/beautifulsoup4-4.12.3-py3-none-any.whl")
   338  	if err != nil {
   339  		t.Fatalf("Error reading content from test file: %v", err)
   340  	}
   341  	srv.SetResponse(t, "/beautifulsoup4-4.12.3-py3-none-any.whl", content)
   342  
   343  	vk := resolve.VersionKey{
   344  		PackageKey: resolve.PackageKey{
   345  			System: resolve.PyPI,
   346  			Name:   "beautifulsoup4",
   347  		},
   348  		Version:     "4.12.3",
   349  		VersionType: resolve.Concrete,
   350  	}
   351  	client := resolution.NewPyPIRegistryClient(srv.URL, "")
   352  	got, err := client.Requirements(t.Context(), vk)
   353  	if err != nil {
   354  		t.Fatalf("failed to get requirements %v: %v", vk, err)
   355  	}
   356  
   357  	depType := func(extra string) dep.Type {
   358  		typ := dep.NewType()
   359  		typ.AddAttr(dep.Environment, "extra == '"+extra+"'")
   360  		return typ
   361  	}
   362  
   363  	want := []resolve.RequirementVersion{
   364  		{
   365  			VersionKey: resolve.VersionKey{
   366  				PackageKey: resolve.PackageKey{
   367  					System: resolve.PyPI,
   368  					Name:   "soupsieve",
   369  				},
   370  				Version:     ">1.2",
   371  				VersionType: resolve.Requirement,
   372  			},
   373  		},
   374  		{
   375  			VersionKey: resolve.VersionKey{
   376  				PackageKey: resolve.PackageKey{
   377  					System: resolve.PyPI,
   378  					Name:   "cchardet",
   379  				},
   380  				Version:     "",
   381  				VersionType: resolve.Requirement,
   382  			},
   383  			Type: depType("cchardet"),
   384  		},
   385  		{
   386  			VersionKey: resolve.VersionKey{
   387  				PackageKey: resolve.PackageKey{
   388  					System: resolve.PyPI,
   389  					Name:   "chardet",
   390  				},
   391  				Version:     "",
   392  				VersionType: resolve.Requirement,
   393  			},
   394  			Type: depType("chardet"),
   395  		},
   396  		{
   397  			VersionKey: resolve.VersionKey{
   398  				PackageKey: resolve.PackageKey{
   399  					System: resolve.PyPI,
   400  					Name:   "charset-normalizer",
   401  				},
   402  				Version:     "",
   403  				VersionType: resolve.Requirement,
   404  			},
   405  			Type: depType("charset-normalizer"),
   406  		},
   407  		{
   408  			VersionKey: resolve.VersionKey{
   409  				PackageKey: resolve.PackageKey{
   410  					System: resolve.PyPI,
   411  					Name:   "html5lib",
   412  				},
   413  				Version:     "",
   414  				VersionType: resolve.Requirement,
   415  			},
   416  			Type: depType("html5lib"),
   417  		},
   418  		{
   419  			VersionKey: resolve.VersionKey{
   420  				PackageKey: resolve.PackageKey{
   421  					System: resolve.PyPI,
   422  					Name:   "lxml",
   423  				},
   424  				Version:     "",
   425  				VersionType: resolve.Requirement,
   426  			},
   427  			Type: depType("lxml"),
   428  		},
   429  	}
   430  	if diff := cmp.Diff(want, got); diff != "" {
   431  		t.Errorf("Requirements(%v) mismatch (-want +got):\n%s", vk, diff)
   432  	}
   433  }