From aedfc80fa5a8499007772f5b009719cc3b6de5f1 Mon Sep 17 00:00:00 2001
From: Franck Bret <franck.bret@octobus.net>
Date: Tue, 2 Aug 2022 09:21:24 +0200
Subject: [PATCH] crates: Loader implements incremental mode

Add incremental support based on sha256 EXTID
Manage release date for each versions of a package
Adapt test dataset and add incremental test cases

Related T4104
---
 docs/package-loader-specifications.rst        |  10 +-
 swh/loader/package/crates/loader.py           | 300 ++++--------
 .../package/crates/tests/data/expected.json   | 133 +++++
 .../package/crates/tests/data/fake_crates.sh  |  15 +-
 .../https_crates.io/api_v1_crates_hg-core     |   2 -
 .../https_crates.io/api_v1_crates_micro-timer |   2 -
 .../crates_hg-core_hg-core-0.0.1.crate        | Bin 426 -> 427 bytes
 ...crates_micro-timer_micro-timer-0.1.0.crate | Bin 456 -> 484 bytes
 ...crates_micro-timer_micro-timer-0.1.1.crate | Bin 458 -> 456 bytes
 ...crates_micro-timer_micro-timer-0.1.2.crate | Bin 485 -> 484 bytes
 ...crates_micro-timer_micro-timer-0.2.0.crate | Bin 419 -> 419 bytes
 ...crates_micro-timer_micro-timer-0.2.1.crate | Bin 420 -> 420 bytes
 ...crates_micro-timer_micro-timer-0.3.0.crate | Bin 413 -> 419 bytes
 ...crates_micro-timer_micro-timer-0.3.1.crate | Bin 421 -> 416 bytes
 ...crates_micro-timer_micro-timer-0.4.0.crate | Bin 417 -> 419 bytes
 .../package/crates/tests/test_crates.py       | 456 +++++++++++++-----
 swh/loader/package/crates/tests/test_tasks.py |  18 +-
 17 files changed, 585 insertions(+), 351 deletions(-)
 create mode 100644 swh/loader/package/crates/tests/data/expected.json
 delete mode 100644 swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_hg-core
 delete mode 100644 swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_micro-timer

diff --git a/docs/package-loader-specifications.rst b/docs/package-loader-specifications.rst
index 01abe7df..25c2bf11 100644
--- a/docs/package-loader-specifications.rst
+++ b/docs/package-loader-specifications.rst
@@ -67,13 +67,13 @@ Here is an overview of the fields (+ internal version name + branch name) used b
      - metadata is intrinsic
    * - crates
      - ``p_info.​version``
-     - ``release_name(​version, filename) + "\n\n" + i_metadata.description + "\n"``
+     - ``release_name(​version)``
      - =version
-     - Synthetic release for Crate source package {p_info.name} version {p_info.version} {description}
+     - Synthetic release for Crate source package {p_info.name} version {p_info.version}
      - true
-     - from int metadata
-     - from ext metadata
-     - ``i_metadata`` for intrinsic metadata, ``e_metadata`` for extrinsic metadata
+     - from intrinsic metadata
+     - from extrinsic metadata
+     - ""
    * - debian
      - =``version``
      - ``release_name(​version)``
diff --git a/swh/loader/package/crates/loader.py b/swh/loader/package/crates/loader.py
index 2943ae9d..51a513b2 100644
--- a/swh/loader/package/crates/loader.py
+++ b/swh/loader/package/crates/loader.py
@@ -3,139 +3,35 @@
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
 
-from distutils.version import StrictVersion
+from datetime import datetime
 import json
 from pathlib import Path
+import string
 from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple
 from urllib.parse import urlparse
 
 import attr
+from packaging.version import parse as parse_version
 import toml
-from typing_extensions import TypedDict
 
-from swh.loader.package.loader import BasePackageInfo, PackageLoader
-from swh.loader.package.utils import cached_method, get_url_body, release_name
-from swh.model.model import ObjectType, Person, Release, Sha1Git, TimestampWithTimezone
+from swh.loader.package.loader import (
+    BasePackageInfo,
+    PackageLoader,
+    RawExtrinsicMetadataCore,
+)
+from swh.loader.package.utils import EMPTY_AUTHOR, release_name
+from swh.model.model import (
+    MetadataAuthority,
+    MetadataAuthorityType,
+    ObjectType,
+    Person,
+    Release,
+    Sha1Git,
+    TimestampWithTimezone,
+)
 from swh.storage.interface import StorageInterface
 
 
-class ExtrinsicPackageMetadata(TypedDict):
-    """Data structure for package extrinsic metadata pulled from http api endpoint.
-
-    We set only the keys we need according to what is available when querying
-    https://crates.io/api/v1/crates/<name>, where `name` is the name of the crate
-    package (see JSON response example at https://crates.io/api/v1/crates/hg-core).
-
-    Usage example:
-
-    .. code-block:: python
-
-        e_metadata = ExtrinsicPackageMetadata(**self.info())
-
-    """  # noqa
-
-    categories: List[Dict[Any, Any]]
-    """Related categories"""
-
-    crate: Dict[Any, Any]
-    """Crate project information"""
-
-    keywords: List[Any]
-    """Keywords"""
-
-    versions: List[Dict[Any, Any]]
-    """A list of released versions for a crate"""
-
-
-class ExtrinsicVersionPackageMetadata(TypedDict):
-    """Data structure for specific package version extrinsic metadata, pulled
-    from http api endpoint.
-
-    Similar to `ExtrinsicPackageMetadata` in its usage, but we flatten the data
-    related to a specific version.
-    """
-
-    crate: str
-    """The package name"""
-
-    crate_size: int
-    """The package size"""
-
-    created_at: str
-    """First released at"""
-
-    downloads: str
-    """Number of downloads"""
-
-    license: str
-    """Package license"""
-
-    num: str
-    """Package version"""
-
-    published_by: Dict[Any, Any]
-    """Publishers information"""
-
-    updated_at: str
-    """Last update"""
-
-    yanked: bool
-    """Is that version yanked? (yanked means release-level deprecation)"""
-
-
-class IntrinsicPackageMetadata(TypedDict):
-    """Data structure for specific package version intrinsic metadata.
-
-    Data is extracted from the crate package's .toml file. Then the data of the
-    'package' entry is flattened.
-
-    Cargo.toml file content example:
-
-    .. code-block:: toml
-
-        [package]
-        name = "hg-core"
-        version = "0.0.1"
-        authors = ["Georges Racinet <georges.racinet@octobus.net>"]
-        description = "Mercurial pure Rust core library, with no assumption on
-        Python bindings (FFI)"
-        homepage = "https://mercurial-scm.org"
-        license = "GPL-2.0-or-later"
-        repository = "https://www.mercurial-scm.org/repo/hg"
-
-        [lib]
-        name = "hg"
-        [dev-dependencies.rand]
-        version = "~0.6"
-
-        [dev-dependencies.rand_pcg]
-        version = "~0.1"
-
-    :param toml: toml object
-    """
-
-    name: str
-    """The package name"""
-
-    version: str
-    """Package version"""
-
-    authors: List[str]
-    """Authors"""
-
-    description: str
-    """Package and release description"""
-
-    homepage: str
-    """Homepage of the project"""
-
-    license: str
-    """Package license"""
-
-    repository: str
-    """Source code repository"""
-
-
 @attr.s
 class CratesPackageInfo(BasePackageInfo):
 
@@ -145,16 +41,20 @@ class CratesPackageInfo(BasePackageInfo):
     version = attr.ib(type=str)
     """Current version"""
 
-    e_metadata: Dict[str, Any] = attr.ib(factory=ExtrinsicPackageMetadata)
-    """Extrinsic package metadata, common to all versions"""
+    sha256 = attr.ib(type=str)
+    """Extid as sha256"""
 
-    e_metadata_version: Dict[str, Any] = attr.ib(
-        factory=ExtrinsicVersionPackageMetadata
-    )
-    """Extrinsic package metadata specific to a version"""
+    last_update = attr.ib(type=datetime)
+    """Last update as release date"""
 
-    i_metadata: Dict[str, Any] = attr.ib(factory=IntrinsicPackageMetadata)
-    """Intrinsic metadata of the current package version"""
+    yanked = attr.ib(type=bool)
+    """Whether the package is yanked or not"""
+
+    MANIFEST_FORMAT = string.Template(
+        "name $name\nshasum $sha256\nurl $url\nversion $version\nlast_update $last_update"
+    )
+    EXTID_TYPE = "crates-manifest-sha256"
+    EXTID_VERSION = 0
 
 
 def extract_intrinsic_metadata(dir_path: Path) -> Dict[str, Any]:
@@ -171,35 +71,6 @@ def extract_intrinsic_metadata(dir_path: Path) -> Dict[str, Any]:
     return toml.load(dir_path / "Cargo.toml")
 
 
-def extract_author(p_info: CratesPackageInfo) -> Person:
-    """Extract package author from intrinsic metadata and return it as a
-    `Person` model.
-
-    Args:
-        p_info: CratesPackageInfo that should contains i_metadata entries
-
-    Returns:
-        Only one author (Person) of the package. Currently limited by internal detail
-        of the swh stack (see T3887).
-    """
-    authors = p_info.i_metadata["authors"]
-    fullname = authors[0]  # TODO: here we have a list of author, see T3887
-    return Person.from_fullname(fullname.encode())
-
-
-def extract_description(p_info: CratesPackageInfo) -> str:
-    """Extract package description from intrinsic metadata and return it as a
-    string.
-
-    Args:
-        p_info: CratesPackageInfo that should contains i_metadata and entries
-
-    Returns:
-        Package description from metadata.
-    """
-    return p_info.i_metadata["description"]
-
-
 class CratesLoader(PackageLoader[CratesPackageInfo]):
     """Load Crates package origins into swh archive."""
 
@@ -210,6 +81,7 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
         storage: StorageInterface,
         url: str,
         artifacts: List[Dict[str, Any]],
+        crates_metadata: List[Dict[str, Any]],
         **kwargs,
     ):
         """Constructor
@@ -223,8 +95,8 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
                 A list of dict listing all existing released versions for a
                 package (Usually set with crates lister `extra_loader_arguments`).
                 Each line is a dict that should have an `url`
-                (where to download package specific version) and a `version` entry.
-
+                (where to download package specific version), a `version`, a
+                `filename` and a `checksums['sha256']` entry.
 
                 Example::
 
@@ -232,29 +104,34 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
                         {
                             "version": <version>,
                             "url": "https://static.crates.io/crates/<package_name>/<package_name>-<version>.crate",
+                            "filename": "<package_name>-<version>.crate",
+                            "checksums": {
+                                "sha256": "<sha256>",
+                            },
                         }
                     ]
+
+            crates_metadata:
+                Same as previously but for Crates metadata.
+                For now it only has one boolean key `yanked`.
+
+                Example::
+
+                    [
+                        {
+                            "version": "<version>",
+                            "yanked": <yanked>,
+                        },
+                    ]
         """  # noqa
         super().__init__(storage=storage, url=url, **kwargs)
         self.url = url
         self.artifacts: Dict[str, Dict] = {
             artifact["version"]: artifact for artifact in artifacts
         }
-
-    @cached_method
-    def _raw_info(self) -> bytes:
-        """Get crate metadata (fetched from http api endpoint set as self.url)
-
-        Returns:
-            Content response as bytes. Content response is a json document.
-        """
-        return get_url_body(self.url)
-
-    @cached_method
-    def info(self) -> Dict:
-        """Parse http api json response and return the crate metadata information
-        as a Dict."""
-        return json.loads(self._raw_info())
+        self.crates_metadata: Dict[str, Dict] = {
+            data["version"]: data for data in crates_metadata
+        }
 
     def get_versions(self) -> Sequence[str]:
         """Get all released versions of a crate
@@ -267,7 +144,7 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
                 ["0.1.1", "0.10.2"]
         """
         versions = list(self.artifacts.keys())
-        versions.sort(key=StrictVersion)
+        versions.sort(key=parse_version)
         return versions
 
     def get_default_version(self) -> str:
@@ -282,6 +159,12 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
         """
         return self.get_versions()[-1]
 
+    def get_metadata_authority(self):
+        return MetadataAuthority(
+            type=MetadataAuthorityType.FORGE,
+            url="https://crates.io/",
+        )
+
     def get_package_info(self, version: str) -> Iterator[Tuple[str, CratesPackageInfo]]:
         """Get release name and package information from version
 
@@ -291,62 +174,63 @@ class CratesLoader(PackageLoader[CratesPackageInfo]):
         Returns:
             Iterator of tuple (release_name, p_info)
         """
-        artifact = self.artifacts[version]
+        artifact = self.artifacts[version].copy()
         filename = artifact["filename"]
+        assert artifact["checksums"]["sha256"]
+        sha256 = artifact["checksums"]["sha256"]
         package_name = urlparse(self.url).path.split("/")[-1]
         url = artifact["url"]
 
-        # Get extrinsic metadata from http api
-        e_metadata = ExtrinsicPackageMetadata(**self.info())  # type: ignore[misc]
+        crate_metadata = self.crates_metadata[version].copy()
+        yanked = crate_metadata["yanked"]
+        last_update = datetime.fromisoformat(crate_metadata["last_update"])
 
-        # Extract crate info for current version (One .crate file for a given version)
-        (crate_version,) = [
-            crate for crate in e_metadata["versions"] if crate["num"] == version
-        ]
-        e_metadata_version = ExtrinsicVersionPackageMetadata(  # type: ignore[misc]
-            **crate_version
-        )
+        # Remove "version" from artifact to follow "original-artifacts-json" extrinsic
+        # metadata format specifications
+        # See https://docs.softwareheritage.org/devel/swh-storage/extrinsic-metadata-specification.html#extrinsic-metadata-formats  # noqa: B950
+        del artifact["version"]
 
         p_info = CratesPackageInfo(
             name=package_name,
             filename=filename,
             url=url,
             version=version,
-            e_metadata=e_metadata,
-            e_metadata_version=e_metadata_version,
+            sha256=sha256,
+            checksums={"sha256": sha256},
+            yanked=yanked,
+            last_update=last_update,
+            directory_extrinsic_metadata=[
+                RawExtrinsicMetadataCore(
+                    format="crates-package-json",
+                    metadata=json.dumps([crate_metadata]).encode(),
+                ),
+            ],
         )
         yield release_name(version, filename), p_info
 
     def build_release(
         self, p_info: CratesPackageInfo, uncompressed_path: str, directory: Sha1Git
     ) -> Optional[Release]:
+
         # Extract intrinsic metadata from dir_path/Cargo.toml
-        name = p_info.name
-        version = p_info.version
-        dir_path = Path(uncompressed_path, f"{name}-{version}")
-        i_metadata_raw = extract_intrinsic_metadata(dir_path)
-        # Get only corresponding key of IntrinsicPackageMetadata
-        i_metadata_keys = [k for k in IntrinsicPackageMetadata.__annotations__.keys()]
-        # We use data only from "package" entry
-        i_metadata = {
-            k: v for k, v in i_metadata_raw["package"].items() if k in i_metadata_keys
-        }
-        p_info.i_metadata = IntrinsicPackageMetadata(**i_metadata)  # type: ignore[misc]
+        dir_path = Path(uncompressed_path, f"{p_info.name}-{p_info.version}")
+        i_metadata = extract_intrinsic_metadata(dir_path)
+
+        author = EMPTY_AUTHOR
+        authors = i_metadata.get("package", {}).get("authors")
+        if authors and isinstance(authors, list):
+            # TODO: here we have a list of author, see T3887
+            author = Person.from_fullname(authors[0].encode())
 
-        author = extract_author(p_info)
-        description = extract_description(p_info)
         message = (
             f"Synthetic release for Crate source package {p_info.name} "
-            f"version {p_info.version}\n\n"
-            f"{description}\n"
+            f"version {p_info.version}\n"
         )
-        # The only way to get a value for updated_at is through extrinsic metadata
-        updated_at = p_info.e_metadata_version.get("updated_at")
 
         return Release(
-            name=version.encode(),
+            name=p_info.version.encode(),
+            date=TimestampWithTimezone.from_datetime(p_info.last_update),
             author=author,
-            date=TimestampWithTimezone.from_iso8601(updated_at),
             message=message.encode(),
             target_type=ObjectType.DIRECTORY,
             target=directory,
diff --git a/swh/loader/package/crates/tests/data/expected.json b/swh/loader/package/crates/tests/data/expected.json
new file mode 100644
index 00000000..7cb069f5
--- /dev/null
+++ b/swh/loader/package/crates/tests/data/expected.json
@@ -0,0 +1,133 @@
+[
+  {
+    "url": "https://crates.io/api/v1/crates/hg-core",
+    "artifacts": [
+      {
+        "version": "0.0.1",
+        "checksums": {
+          "sha256": "233409cca39eccab8075d3414884a2e9096aa3f7ceb4139685134c9f2561e734"
+        },
+        "filename": "hg-core-0.0.1.crate",
+        "url": "https://static.crates.io/crates/hg-core/hg-core-0.0.1.crate"
+      }
+    ],
+    "crates_metadata": [
+      {
+        "version": "0.0.1",
+        "yanked": false,
+        "last_update": "2019-04-16T18:48:11.404457+00:00"
+      }
+    ]
+  },
+  {
+    "url": "https://crates.io/api/v1/crates/micro-timer",
+    "artifacts": [
+      {
+        "version": "0.1.0",
+        "checksums": {
+          "sha256": "837c3955f0dfbd1a95f4388e06acb2f1a8030e28d251867e603b6be7b93da783"
+        },
+        "filename": "micro-timer-0.1.0.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.0.crate"
+      },
+      {
+        "version": "0.1.1",
+        "checksums": {
+          "sha256": "bfb0e972e961d6bfd8d344ccf05c29ae39b7d817ced30568204495aee4bab519"
+        },
+        "filename": "micro-timer-0.1.1.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.1.crate"
+      },
+      {
+        "version": "0.1.2",
+        "checksums": {
+          "sha256": "53251c2ab69c97447b4d6bfb327f7608ba0f7108e4aa3d4f559934d6cd088d11"
+        },
+        "filename": "micro-timer-0.1.2.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.2.crate"
+      },
+      {
+        "version": "0.2.0",
+        "checksums": {
+          "sha256": "d5a39e92178f6f0c8acb1111258327eab2447e2ab574e5868e2770ed28940d70"
+        },
+        "filename": "micro-timer-0.2.0.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.2.0.crate"
+      },
+      {
+        "version": "0.2.1",
+        "checksums": {
+          "sha256": "7889cfebb345bd706ecb93e7b67e7e6af1d68ce16e3f0c109bf8b7bcb79e55af"
+        },
+        "filename": "micro-timer-0.2.1.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.2.1.crate"
+      },
+      {
+        "version": "0.3.0",
+        "checksums": {
+          "sha256": "d92b84c26f807891fd492ceecc11ca2bdaf73583665275080ead1c127ed861a4"
+        },
+        "filename": "micro-timer-0.3.0.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.3.0.crate"
+      },
+      {
+        "version": "0.3.1",
+        "checksums": {
+          "sha256": "740acae0cfa83cf78207aab16ba4a41dce7101bca774c63eac8c41fd5ce382a5"
+        },
+        "filename": "micro-timer-0.3.1.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.3.1.crate"
+      },
+      {
+        "version": "0.4.0",
+        "checksums": {
+          "sha256": "475222c2def55b7166390398a0c033ceb9a1cdfe008ff6ebd22773fb2029a60f"
+        },
+        "filename": "micro-timer-0.4.0.crate",
+        "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.4.0.crate"
+      }
+    ],
+    "crates_metadata": [
+      {
+        "version": "0.1.0",
+        "yanked": false,
+        "last_update": "2020-02-27T14:31:49.131258+00:00"
+      },
+      {
+        "version": "0.1.1",
+        "yanked": false,
+        "last_update": "2020-02-27T15:17:53.486346+00:00"
+      },
+      {
+        "version": "0.1.2",
+        "yanked": false,
+        "last_update": "2020-02-27T23:35:41.872176+00:00"
+      },
+      {
+        "version": "0.2.0",
+        "yanked": false,
+        "last_update": "2020-03-23T10:57:04.418462+00:00"
+      },
+      {
+        "version": "0.2.1",
+        "yanked": false,
+        "last_update": "2020-03-23T11:22:26.288804+00:00"
+      },
+      {
+        "version": "0.3.0",
+        "yanked": false,
+        "last_update": "2020-06-02T11:38:33.047581+00:00"
+      },
+      {
+        "version": "0.3.1",
+        "yanked": false,
+        "last_update": "2020-06-22T16:40:06.754009+00:00"
+      },
+      {
+        "version": "0.4.0",
+        "yanked": false,
+        "last_update": "2020-09-28T13:40:49.593030+00:00"
+      }
+    ]
+  }
+]
diff --git a/swh/loader/package/crates/tests/data/fake_crates.sh b/swh/loader/package/crates/tests/data/fake_crates.sh
index 9bfff253..c0eba7b4 100644
--- a/swh/loader/package/crates/tests/data/fake_crates.sh
+++ b/swh/loader/package/crates/tests/data/fake_crates.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 
-# Script to generate fake crates files and fake http api response.
+# Script to generate fake crates files.
 
 set -euo pipefail
 
@@ -45,7 +45,7 @@ echo -e '''[package]
 edition = "2018"
 name = "micro-timer"
 version = "0.1.0"
-authors = ["Raphaël Gomès <rgomes@octobus.net>"]
+# authors = ["Raphaël Gomès <rgomes@octobus.net>"]  # commented for testing empty authors
 description = "Dumb tiny logging timer"
 homepage = "https://heptapod.octobus.net/Alphare/micro-timer"
 readme = "README.md"
@@ -243,17 +243,6 @@ cp micro-timer-0.3.0.crate ../../https_static.crates.io/crates_micro-timer_micro
 cp micro-timer-0.3.1.crate ../../https_static.crates.io/crates_micro-timer_micro-timer-0.3.1.crate
 cp micro-timer-0.4.0.crate ../../https_static.crates.io/crates_micro-timer_micro-timer-0.4.0.crate
 
-# Creates some http file response for test purposes.
-mkdir ../../https_crates.io
-
-# hg-core, https://crates.io/api/v1/crates/hg-core
-echo -e '''{"categories":[],"crate":{"badges":[],"categories":[],"created_at":"2019-04-16T18:48:11.404457+00:00","description":"Mercurial pure Rust core library, with no assumption on Python bindings (FFI)","documentation":null,"downloads":442,"exact_match":false,"homepage":"https://mercurial-scm.org","id":"hg-core","keywords":[],"links":{"owner_team":"/api/v1/crates/hg-core/owner_team","owner_user":"/api/v1/crates/hg-core/owner_user","owners":"/api/v1/crates/hg-core/owners","reverse_dependencies":"/api/v1/crates/hg-core/reverse_dependencies","version_downloads":"/api/v1/crates/hg-core/downloads","versions":null},"max_stable_version":"0.0.1","max_version":"0.0.1","name":"hg-core","newest_version":"0.0.1","recent_downloads":40,"repository":"https://www.mercurial-scm.org/repo/hg","updated_at":"2019-04-16T18:48:11.404457+00:00","versions":[145309]},"keywords":[],"versions":[{"audit_actions":[],"crate":"hg-core","crate_size":21344,"created_at":"2019-04-16T18:48:11.404457+00:00","dl_path":"/api/v1/crates/hg-core/0.0.1/download","downloads":442,"features":{},"id":145309,"license":"GPL-2.0-or-later","links":{"authors":"/api/v1/crates/hg-core/0.0.1/authors","dependencies":"/api/v1/crates/hg-core/0.0.1/dependencies","version_downloads":"/api/v1/crates/hg-core/0.0.1/downloads"},"num":"0.0.1","published_by":{"avatar":"https://avatars0.githubusercontent.com/u/474220?v=4","id":45544,"login":"gracinet","name":"Georges Racinet","url":"https://github.com/gracinet"},"readme_path":"/api/v1/crates/hg-core/0.0.1/readme","updated_at":"2019-04-16T18:48:11.404457+00:00","yanked":false}]}
-''' > ../../https_crates.io/api_v1_crates_hg-core
-
-# micro-timer, https://crates.io/api/v1/crates/micro-timer
-echo -e '''{"categories":[],"crate":{"badges":[],"categories":[],"created_at":"2020-02-27T14:31:49.131258+00:00","description":"Dumb tiny logging timer","documentation":null,"downloads":44245,"exact_match":false,"homepage":"https://foss.heptapod.net/octobus/rust/micro-timer","id":"micro-timer","keywords":[],"links":{"owner_team":"/api/v1/crates/micro-timer/owner_team","owner_user":"/api/v1/crates/micro-timer/owner_user","owners":"/api/v1/crates/micro-timer/owners","reverse_dependencies":"/api/v1/crates/micro-timer/reverse_dependencies","version_downloads":"/api/v1/crates/micro-timer/downloads","versions":null},"max_stable_version":"0.4.0","max_version":"0.4.0","name":"micro-timer","newest_version":"0.4.0","recent_downloads":3910,"repository":"https://foss.heptapod.net/octobus/rust/micro-timer","updated_at":"2020-09-28T13:40:49.593030+00:00","versions":[288167,254896,248120,223660,223652,216405,216156,216139]},"keywords":[],"versions":[{"audit_actions":[{"action":"publish","time":"2020-09-28T13:40:49.593030+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3513,"created_at":"2020-09-28T13:40:49.593030+00:00","dl_path":"/api/v1/crates/micro-timer/0.4.0/download","downloads":337,"features":{},"id":288167,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.4.0/authors","dependencies":"/api/v1/crates/micro-timer/0.4.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.4.0/downloads"},"num":"0.4.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.4.0/readme","updated_at":"2020-09-28T13:40:49.593030+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-06-22T16:40:06.754009+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3357,"created_at":"2020-06-22T16:40:06.754009+00:00","dl_path":"/api/v1/crates/micro-timer/0.3.1/download","downloads":37853,"features":{},"id":254896,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.3.1/authors","dependencies":"/api/v1/crates/micro-timer/0.3.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.3.1/downloads"},"num":"0.3.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.3.1/readme","updated_at":"2020-06-22T16:40:06.754009+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-06-02T11:38:33.047581+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3306,"created_at":"2020-06-02T11:38:33.047581+00:00","dl_path":"/api/v1/crates/micro-timer/0.3.0/download","downloads":4163,"features":{},"id":248120,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.3.0/authors","dependencies":"/api/v1/crates/micro-timer/0.3.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.3.0/downloads"},"num":"0.3.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.3.0/readme","updated_at":"2020-06-02T11:38:33.047581+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-03-23T11:22:26.288804+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2937,"created_at":"2020-03-23T11:22:26.288804+00:00","dl_path":"/api/v1/crates/micro-timer/0.2.1/download","downloads":1301,"features":{},"id":223660,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.2.1/authors","dependencies":"/api/v1/crates/micro-timer/0.2.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.2.1/downloads"},"num":"0.2.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.2.1/readme","updated_at":"2020-03-23T11:22:26.288804+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-03-23T10:57:04.418462+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2941,"created_at":"2020-03-23T10:57:04.418462+00:00","dl_path":"/api/v1/crates/micro-timer/0.2.0/download","downloads":104,"features":{},"id":223652,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.2.0/authors","dependencies":"/api/v1/crates/micro-timer/0.2.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.2.0/downloads"},"num":"0.2.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.2.0/readme","updated_at":"2020-03-23T10:57:04.418462+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T23:35:41.872176+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":4927,"created_at":"2020-02-27T23:35:41.872176+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.2/download","downloads":258,"features":{},"id":216405,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.2/authors","dependencies":"/api/v1/crates/micro-timer/0.1.2/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.2/downloads"},"num":"0.1.2","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.2/readme","updated_at":"2020-02-27T23:35:41.872176+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T15:17:53.486346+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2916,"created_at":"2020-02-27T15:17:53.486346+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.1/download","downloads":111,"features":{},"id":216156,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.1/authors","dependencies":"/api/v1/crates/micro-timer/0.1.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.1/downloads"},"num":"0.1.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.1/readme","updated_at":"2020-02-27T15:17:53.486346+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T14:31:49.131258+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2902,"created_at":"2020-02-27T14:31:49.131258+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.0/download","downloads":118,"features":{},"id":216139,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.0/authors","dependencies":"/api/v1/crates/micro-timer/0.1.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.0/downloads"},"num":"0.1.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.0/readme","updated_at":"2020-02-27T14:31:49.131258+00:00","yanked":false}]}
-''' > ../../https_crates.io/api_v1_crates_micro-timer
-
 # Clean up removing tmp_dir
 cd ../../
 rm -r tmp_dir/
diff --git a/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_hg-core b/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_hg-core
deleted file mode 100644
index c76874d8..00000000
--- a/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_hg-core
+++ /dev/null
@@ -1,2 +0,0 @@
-{"categories":[],"crate":{"badges":[],"categories":[],"created_at":"2019-04-16T18:48:11.404457+00:00","description":"Mercurial pure Rust core library, with no assumption on Python bindings (FFI)","documentation":null,"downloads":442,"exact_match":false,"homepage":"https://mercurial-scm.org","id":"hg-core","keywords":[],"links":{"owner_team":"/api/v1/crates/hg-core/owner_team","owner_user":"/api/v1/crates/hg-core/owner_user","owners":"/api/v1/crates/hg-core/owners","reverse_dependencies":"/api/v1/crates/hg-core/reverse_dependencies","version_downloads":"/api/v1/crates/hg-core/downloads","versions":null},"max_stable_version":"0.0.1","max_version":"0.0.1","name":"hg-core","newest_version":"0.0.1","recent_downloads":40,"repository":"https://www.mercurial-scm.org/repo/hg","updated_at":"2019-04-16T18:48:11.404457+00:00","versions":[145309]},"keywords":[],"versions":[{"audit_actions":[],"crate":"hg-core","crate_size":21344,"created_at":"2019-04-16T18:48:11.404457+00:00","dl_path":"/api/v1/crates/hg-core/0.0.1/download","downloads":442,"features":{},"id":145309,"license":"GPL-2.0-or-later","links":{"authors":"/api/v1/crates/hg-core/0.0.1/authors","dependencies":"/api/v1/crates/hg-core/0.0.1/dependencies","version_downloads":"/api/v1/crates/hg-core/0.0.1/downloads"},"num":"0.0.1","published_by":{"avatar":"https://avatars0.githubusercontent.com/u/474220?v=4","id":45544,"login":"gracinet","name":"Georges Racinet","url":"https://github.com/gracinet"},"readme_path":"/api/v1/crates/hg-core/0.0.1/readme","updated_at":"2019-04-16T18:48:11.404457+00:00","yanked":false}]}
-
diff --git a/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_micro-timer b/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_micro-timer
deleted file mode 100644
index e6878b68..00000000
--- a/swh/loader/package/crates/tests/data/https_crates.io/api_v1_crates_micro-timer
+++ /dev/null
@@ -1,2 +0,0 @@
-{"categories":[],"crate":{"badges":[],"categories":[],"created_at":"2020-02-27T14:31:49.131258+00:00","description":"Dumb tiny logging timer","documentation":null,"downloads":44245,"exact_match":false,"homepage":"https://foss.heptapod.net/octobus/rust/micro-timer","id":"micro-timer","keywords":[],"links":{"owner_team":"/api/v1/crates/micro-timer/owner_team","owner_user":"/api/v1/crates/micro-timer/owner_user","owners":"/api/v1/crates/micro-timer/owners","reverse_dependencies":"/api/v1/crates/micro-timer/reverse_dependencies","version_downloads":"/api/v1/crates/micro-timer/downloads","versions":null},"max_stable_version":"0.4.0","max_version":"0.4.0","name":"micro-timer","newest_version":"0.4.0","recent_downloads":3910,"repository":"https://foss.heptapod.net/octobus/rust/micro-timer","updated_at":"2020-09-28T13:40:49.593030+00:00","versions":[288167,254896,248120,223660,223652,216405,216156,216139]},"keywords":[],"versions":[{"audit_actions":[{"action":"publish","time":"2020-09-28T13:40:49.593030+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3513,"created_at":"2020-09-28T13:40:49.593030+00:00","dl_path":"/api/v1/crates/micro-timer/0.4.0/download","downloads":337,"features":{},"id":288167,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.4.0/authors","dependencies":"/api/v1/crates/micro-timer/0.4.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.4.0/downloads"},"num":"0.4.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.4.0/readme","updated_at":"2020-09-28T13:40:49.593030+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-06-22T16:40:06.754009+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3357,"created_at":"2020-06-22T16:40:06.754009+00:00","dl_path":"/api/v1/crates/micro-timer/0.3.1/download","downloads":37853,"features":{},"id":254896,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.3.1/authors","dependencies":"/api/v1/crates/micro-timer/0.3.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.3.1/downloads"},"num":"0.3.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.3.1/readme","updated_at":"2020-06-22T16:40:06.754009+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-06-02T11:38:33.047581+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":3306,"created_at":"2020-06-02T11:38:33.047581+00:00","dl_path":"/api/v1/crates/micro-timer/0.3.0/download","downloads":4163,"features":{},"id":248120,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.3.0/authors","dependencies":"/api/v1/crates/micro-timer/0.3.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.3.0/downloads"},"num":"0.3.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.3.0/readme","updated_at":"2020-06-02T11:38:33.047581+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-03-23T11:22:26.288804+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2937,"created_at":"2020-03-23T11:22:26.288804+00:00","dl_path":"/api/v1/crates/micro-timer/0.2.1/download","downloads":1301,"features":{},"id":223660,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.2.1/authors","dependencies":"/api/v1/crates/micro-timer/0.2.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.2.1/downloads"},"num":"0.2.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.2.1/readme","updated_at":"2020-03-23T11:22:26.288804+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-03-23T10:57:04.418462+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2941,"created_at":"2020-03-23T10:57:04.418462+00:00","dl_path":"/api/v1/crates/micro-timer/0.2.0/download","downloads":104,"features":{},"id":223652,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.2.0/authors","dependencies":"/api/v1/crates/micro-timer/0.2.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.2.0/downloads"},"num":"0.2.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.2.0/readme","updated_at":"2020-03-23T10:57:04.418462+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T23:35:41.872176+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":4927,"created_at":"2020-02-27T23:35:41.872176+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.2/download","downloads":258,"features":{},"id":216405,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.2/authors","dependencies":"/api/v1/crates/micro-timer/0.1.2/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.2/downloads"},"num":"0.1.2","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.2/readme","updated_at":"2020-02-27T23:35:41.872176+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T15:17:53.486346+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2916,"created_at":"2020-02-27T15:17:53.486346+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.1/download","downloads":111,"features":{},"id":216156,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.1/authors","dependencies":"/api/v1/crates/micro-timer/0.1.1/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.1/downloads"},"num":"0.1.1","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.1/readme","updated_at":"2020-02-27T15:17:53.486346+00:00","yanked":false},{"audit_actions":[{"action":"publish","time":"2020-02-27T14:31:49.131258+00:00","user":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"}}],"crate":"micro-timer","crate_size":2902,"created_at":"2020-02-27T14:31:49.131258+00:00","dl_path":"/api/v1/crates/micro-timer/0.1.0/download","downloads":118,"features":{},"id":216139,"license":"non-standard","links":{"authors":"/api/v1/crates/micro-timer/0.1.0/authors","dependencies":"/api/v1/crates/micro-timer/0.1.0/dependencies","version_downloads":"/api/v1/crates/micro-timer/0.1.0/downloads"},"num":"0.1.0","published_by":{"avatar":"https://avatars.githubusercontent.com/u/9445758?v=4","id":79957,"login":"Alphare","name":"Raphaël Gomès","url":"https://github.com/Alphare"},"readme_path":"/api/v1/crates/micro-timer/0.1.0/readme","updated_at":"2020-02-27T14:31:49.131258+00:00","yanked":false}]}
-
diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_hg-core_hg-core-0.0.1.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_hg-core_hg-core-0.0.1.crate
index e51e4372bfc9817a3cac1ee5f9be8d549ae8d479..d55446c3398f4a4b66695760dbba79eb7d5cef0c 100644
GIT binary patch
literal 427
zcmV;c0aX4UiwFP!000001MSjLYuhjo0AN4sR|q|AFw1sqCor}#7_<yFD18lKDDuq~
zwIy?s8}hQ>J~?S2Z5Q^^vW?w`!A3rvPC9jMsvSa_%Ul*&@*?tRRoC;oSk?LI`(9F1
zWl@y*sw!h#u_za-gx4=q&O?(A3OP@{qq6$ueBAH;|D(DG{r{w}buzf2f6g`L1~04X
zZ2y;a`KbTZVp%3Uf6gM#`Oo{m9+kdPEo_)o1Mv4;+}#MlZUMb<b{;$24#CtEI)^@v
zti>fbY{BzurHzHa-?hg9iN}GDP6xN0yo}!;#D+ECHJUNpW&8@LCp4<(<AlJkV;lIq
zEbq-074F~goe3Scjw|n{;W#foSNm9k<y&JLV_VN(U0i&AEm-FU7~?h$8wg?aZ?kN;
zUn2E-kg+(ydZWSmL)7KfmvkZX)M47I04Ny3=)4II_orEQyPbTJDVt-nE@ouwSn%oL
z3AS$FHf>-8+kn;Pa3r?boSx^8TrTIizl{7g>h^JTJbd=2uL>cA5JCtcgb+dqA%qY@
V2qA<JLJ0W_{RA<$TCD&m004?a(Bc39

literal 426
zcmV;b0agAViwFP!000001MSjZYuhjo0B}F+QwTkEF#nGo7~3cmS_&JKzJ@Rq`DTmS
zk~zr@dD&;5oV1X(3wvqV#_or~Mn0WRI(2QVEJB<mS(2yGo5+G?Rn6~wS!KuXM^U~g
z%6wT=WnM>FUX*1K@#<~Ld1`WABInU}lt$g1j{DvJe^mFR{}&Qln|M3)uerwD;Cit*
z+5fsKpY?xH)OEzO*DT_c|GfXJQK~!H!kQU50RP0r!;KK^9?)rP=CS?l5KK<Kv*^Oe
zN?d`(7978oN*nO}V|y4#@Gx+0mA9M8CE@$CShEJ4LOuG2j9&rOgj)7|oDleJXak>@
z<-OjZ#NB(o)xP7#a_QVO9Oi}RW*15@e4|aHP3!pC<>mD|!8$v@7`CzBfcK;Okfy`q
z60uW55{eV7*9wf=M_t`~iHjtQEylg{fPx{6)@g5XcbsLr-6k(GrE_fBg^X+!3O+tO
z!B!32#|?~N8Zb)lkHj?V<MaHHCG|Y_myzE_)jp37htK}>RUw2BLI@#*5JCtcgb+dq
UA%qY@2qAx=pRPhKuK*|j01*t<@&Et;

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.0.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.0.crate
index dffc2fc86ec38139ee956d8009280c830d215dbf..c7cfa0f645b6aa1d655e08ccdd6f1755a68d76f2 100644
GIT binary patch
delta 471
zcmV;|0Vw{+1LOmc7k_)L|6(U8r7c~up%mI4_8NRp?6ED@`l%!@%^MZ^82e(C_Xm=O
zw6Fx0()qv`TO(<t87XS3V=pAwI7{**OQW;Pg5`WZz4PTf+kZca^0LVDB3qV4h|7yv
zF^lN@{HYupo#&R)sI^?xH>czNHvW&2NA+KE+v&t>Ih<>cseizWvOHD)JS&&K)qkEZ
z%7|v?O5jia^ZIWLuWxt<6@f;0p%r~c*(}RnGorYJ>1(e=7`X-O9;5Etg^>$B`d(WX
zer?z_H$A`mIna+<-u-a&ZRjNl?t`wq-i|I&;NP?Af(qq9d7=2QqqUYYd_Y56ZK((6
zsTr-&4oCy8wtvDr<#jd6Ee*4FbkJQVR7W4P>cb``oMH;<y*KVnn)YBkH@Zpo_D(N{
zke`M0;7k^H^KjJb_2t!<bs`%^22q1@5VvBuANu@hwf?$VPthSY;kDg8Z#S|T#I_>F
z>N=J@41{I89f52bFrXSxwE&lV8?_Hr*}Ly>+sSO2e^3zTcIqf9Uog@F_oIb}vb5tc
zuve79cW-$d9^oCUrj>5vgVn;lR);k%lh6v}rP~xCgb+dqA%qY@2qA<JLI@#*5JCu@
N@C(1WjX3})006rd>)-$Y

delta 443
zcmV;s0Yv`f1IPoA7k}saH*s7_Te@UJDYQN8HRPb!6I-nQD#=UpMuk4czF6h`fn>wB
z&<0BBd|-sFkw%`860I|75DHvcWMx+5$=MZAQ`gg2Hg$10K1#}YH7lE{o|TKFC}-8I
zPH27pqnsLj;EvK{=eTaKpO5?1{69*a^nb}+Z?a&NJXemX!GDYS{Mr6D&EiS_>#}JQ
zTAb?uKl7jWe{Fet&3o7o=tK}k(>Ih=Mfs8u%@s^<2UmoV8*u(1>h8KQ!pATe=i`?(
zyW)1>x8EgwZ`AEKPhZEYq`<#3Z7|!>XBxs=wjmw()(QKV?s8OH8id|cY5HF1o<1ZS
z;$j;QHw6tLSbzU2&j+x9ThnC+d*&B1=5>%C9n}Hv?*HIwb#eJ=m8p&qDO%7T(w&fZ
zQy)JpSD%-wDcTw@f^qxb%SP5xY&XO@)252YiMUO0Ban3m7IX)?72vZkqY1It2W5;!
z%&KYqQJmlFlc;jR$PRcI9o(0DH%iG~PzGOv<7s?E@HDZ!ai^Q~Xt!9`&2j7IS?n<R
l+qVxPgb+dqA%qY@2qA<JLI@#*5JCw3sUNF^?k)f*0083E+<pK6

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.1.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.1.crate
index 6cc3eb6a24c53be899c9133e4c80d564b46fdd04..a3e861c483f6f7b4e90a107fa039abd29496f98b 100644
GIT binary patch
literal 456
zcmV;(0XP01iwFP!000001MSkyirX*{0N|YU6hi0vN3k83(v~jSPzr4idkr}#_QV!z
z{Zx{d=8Xz{jD4}n`vb{_ZJ`a6()qv$TO*A;BPAPWk{}hhBu{zDv)S3@QC(Hj$m=RU
z93RbiS#VzDby>tXUd)SPMyvB5<<#hdaFot=PUz<P`M6)r|D)7N|ChpbCJjan=gKiP
zcu|(m_P?$dPx@cQ`5Dd6b%3Aw&-=f&qPZ3wYzVY6NTcZ+%H}zL$%qyTrniGD!pIFc
z{}6R|T^JE#=#BI7%bHyY+l$-p1ATAQ?Ke+f$E&2kzcWoR+tH^Q!dtc>E%?Ss`<U)>
zR9hOP-qXQ!ozxwDNY=;2HXd#Y>O-*pRhIQ&gRrJe5BAJ1hM3nuc63w+qP_ovtJTHj
zr&X$2Mh4k{_K@u4a5wew!*ca`xtgM_@iG{<|GjKvJ;?2bSZA6<i8v9r32p?kZoz_X
zK{pb7`eigB7W<%#Jk8VjwEig0@AXL(Uof%*5k?31<=&0Mz+O-WUxO1#d}Q#kym6<S
y<Y>28*UfS3$~1PE{O#L^5JCtcgb+dqA%qY@2qA<JLI@#*{?rdx&Ev@cC;$MhW9P8|

literal 458
zcmV;*0X6;~iwFP!000001MSkyirX*{0AQc>6vF3PwwyRFr7c~up%mI4_8M|f?1?Sb
z`l%!@%^MZ^82e(C_Xm;<+d>;CrSpL?w#FKHW|VB4$%0hivVw8O^W^M`sIIE%&g-f;
zygy3#yqxj6tY&<X6ns|B774A+f0R?B55iHJ?3~ce_49GRTK`9>lm0J->kJD<4d==+
zHFz<fKimJhUOef4RmPE^#kmgfGyi%2*H$#wqJs^ARt9M_eM8f-;4f36g@WnnU_?@K
z1I|B0-Hl61#29+xe0;M`uY~Qz?e~GcH|qAAr?2BEDe&)16U=t>Ohb5^Zb%Ehane4f
zyByV)2C4USFkL5gM<0^)@nahgHwE<}SpO=|d$2)R)3Sp-^NS(ob&ww&)q!a5|KMtM
zartS*RGX4PHlRIZJ2~7fef+RoeO|7nXluL-#_fME8(9x>yCK$@CQ~9V#BG8bfvj7w
zpj*(51kb*VCd6VNl##Q7mDBe}ael8)qWB^uI}l-Xa9{4-I1K3vn!?xML>4a@d@OI=
z=_WhcE!K5&+`2i79VUN!`Vc}0A%qY@2qA<JLI@#*5JCtcgwUV*0lGg`*Z?R10LcL0
AEC2ui

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.2.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.1.2.crate
index 3a0637033f921e8cfaefcc860b181fbdbf6dbcc9..28a7e65bf610f8bbe02c124b140da600cbec6263 100644
GIT binary patch
literal 484
zcmV<A0UQ1wiwFP!000001MSk?YTGar2k>0$DTJ?Q+44uyjcu%?VGOoi^frW{*eABA
zZE2ERmNy#gG5TVYo0i2jZD9n)w)2Bv937pb<gZp1MukqcV8WPjmPE6Yi@imj4^O_x
z<KySO8P8JA(|D1kzK*9!n$2i_`lC$s)(J!DY-dDQTs>a*-1=Xto#ek1rc%tQwmH>|
zA>etIJ(fS`er=q8&gVY=_*4gY%732!T8rXJRInjXN+(rE-_Rh5`B^|Z(ZcX{bVUMk
z4aVM8-Cmb~h~Cx8SpUsBxD>h;H$NKsUbQ#hEq(2;QVaH-DxBK(mUZCX1{+d>EsWIn
z<1YGkOP%cYv{6+hyNccotNmu}4>uIm&T0E9ifYhKXjQVKp3!;Z=NgDcM>QbI!!Nj8
zonL%fv9=6IBMay(gge>XE`9v4Tzy`yhH9;>bjs|1ZyH%Qa=Rhgs3L5IU+`_5>4B_E
z(9o686%s7_wVrH_|4hze_HyVls{Yz52a~#FQfK$wq>9f2vIF6I1BY;SebWRlXaL`w
z5uyJ`XMGsH({(uN=2O^=gU;C5VdjHMZNRzx7hx@!Ax-;W-<)y3?@!-xgb+dqA%qY@
a2qA<JLI@#*5JCu{f7ws)+V+G1C;$L9|MvR;

literal 485
zcmV<B0UG`viwFP!000001MSk$YTGar24G+NDTJ<P*>Ym18{1e(!x(J4=xqo?u}^GK
z+tMVtEN?W}WAw!)H!X{6+QJBoZRdkv937pb<X0;Tqar6;Fj2xdOXJ|=5^s@Z!;>$v
z<oJ0n;PZ6G7il)*d64i~I$H!JJN;3ndh3KCB-k0z6<3efJ-7asYA5+Gg{c&Cs%=g+
zV+c5(&mYU5bH6suKjXR2KRML_p7NjPzt*C-5*2JhD5aCCBX0;z6Mja+PP8z*9bFL`
zUW2iBRkzoLhN5@1GS+{yrk6t3;^s#~-mCWJyCtvvRcgV$Q-xF8-m(tdTe=BLu!WKO
ze%wXhZi$oKo;0eeWLJ^9VYT0^{o#h9+Bt1s#c>VV39U+Y)H6PB{9FU^=%@xndH4mF
ztMiLbE7q1YY-9nQg=i<6+og{mmaEUp)ljXKl}?%c?@bHWjofZRZB!Ap!Y}wX&h!wj
zOVH4j&=nFa`?a2Kj{i)~683WFGOGUCD+iOhbW&&c-K2`=G~9u3y@5kGyS{1Y3qs+W
zGa~XI>8uaKce;*7-Fyn0anN&ic9{8~QX6n?|3z2}W=PXM*q1Zz_x<TRju1i!A%qY@
b2qA<JLI@#*5JCtc^e_7f^cjQC04M+eDYou;

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.2.0.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.2.0.crate
index c304d0445e115d4406b473631066e953070e4e6f..185a165db8b301f87fa1107c7ccab83821cf7651 100644
GIT binary patch
literal 419
zcmV;U0bKqciwFP!000001MSn@YTGar2XJ5eDTMDMTd|##(T#>Al)<`--iBN#_K7WG
zeIUt!zR_Th(HE=SHISt11|zJDo!^Z=^tX?!BTG?PJ@P_=9c6S(voyN6ESQ$%aAeam
zJ3byntjHP5vuTls9LvXf9+C3$Q+cg-o?Akqt>voPy`B5r_&-X%>i?YEMpLh4cc~gf
zgC|AtX8)(-;;jEAn@l2-U1|cq@}KvAWq7sY4Xk6Rg%?_pk0cpq>?(;BmoQu%ZILA2
zgLN;d=k1cjy!WlPF5Foq_uRDn@q0&ZwS4^M$aUCC65Nfhyx#PVD)66@bzFn1tT2ai
zi(YPs7wUm@x@m-J$V;&{JZ!?`hEVIhaUasO1>?EVH9dM~I_pAR3+c&JE%5rs7u+vr
zi@PP2brN@?0_9+|72WgDm(TO%*ZFct8|{SG_TkT}t!gl!YEYE`M~{Ci$-}u@|77XT
z9&(j7(DdBaCt*g}5DwY>=p-Lj>+G!s4SkE>dru*R5JCtcgb+dqA%qY@2qA<JLI|P1
N=Lr>Cqa*+*003xM)HVPB

literal 419
zcmV;U0bKqciwFP!000001MSnzYU3~v0AQc}6vFq&mSe}I>@Hi<uoT)JdJQ?K_S6=&
zevsrs->A^X=!;cu8%WYWD51-;^Ic?HW6fA2OHo@r@j`-~WHhH)8tq*cRAo7itSYn3
zaV=s+K4n#2PT4HV*fgKcB2w<Zl-CC5xg{jJvRu{I@5g;M|BsTd`akEk)zoX*?<>dH
z;8{_;+y5#rcKTnkibW*bR{<aS&-=eLyuRiwtYT<{7g~{@B*`;&ki?2h81FWYND|+G
zbuUp*$0dpR;5%(yc(P0`xas)AeNX;q`EcjRk8qSExZk?=`f_kof&Z1P;s#u8g;}RN
z9^@tQLfw*Hx2;euc}doVmrW>c4C=f$?l4U|FrFLT(2X_Iqdw%dkZzS~fj7@zaIrW#
zK3h=PBylflP!1+nqJNq?J((}g=Zi7gXeYe3w_nS)Y`}nOK-B^q-TbX257%z>#@6k;
z<Z5l89k^|_f*ECFaEQH`Y{iG&+F7-rp>OfuK2r!Ggb+dqA%qY@2qA<JLI@#*5JKqt
NJOWBG0NVg4007KD&P4zK

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.2.1.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.2.1.crate
index 3d844f0cf4edf2f90e5d689b19dacb7a51c63fd0..8e193d425fcd121c5012f3e050499ac0cdf5dcd3 100644
GIT binary patch
literal 420
zcmV;V0bBkbiwFP!000001MSn@YTGar2XJ5eDTMDMTd^IF(v5{Ql)<)(-iBPL_Ngsm
zeIUt&zR_Th(HEPXwU7_8LJ2Kp=Xc`|{p};`$Wqi+PrQ&|CmEemmPTin1yxy&M^=^D
z?(tc~ikz`LtBO42SU$~*h?M7_%CW(DZV8F*ELZjBbnd0`4@w^Of5C05sn@bUSB<g3
zv!XcJ|7uzs^uJ`YDk9mrCh#x+dH+|2*Bjo#I)+Agp%wW|l4-_1C9&cX#;ctzlEn95
z-BW72U6Pm&zSGu)JFDb|n~p#J?#Z>5kG~wb3|mQp`>JcNZwE&e_%F#iZot)6nCEg=
zgS;hPs0Y&PwiT)+PsO_MunChJL!I}=U8HFT#&e?^y7SI--iNvt(!Hr#;LYDJxLMAx
zek`eMlDHQ&C<l`}(Qk*oe_JenE|z23XeYe353j4XYQTVMK-B^q-Tkd359e-uxO7<(
zA3Wr0ZJ-^vZT7;9vN0U8_mjPRSgnJ%7BuuNe(ybn5JCtcgb+dqA%qY@2qA<JLI@#*
O-p(Hb{8qXEC;$K`643|%

literal 420
zcmV;V0bBkbiwFP!000001MSn@YTGar0AOGHDTMDMTaF!%(v5{Al)<)(-iBPL_Ngsm
z{UFJKzR_Th(HEPXwUDHAg%VoI&UYi(I?|DKWGQN^CtgUflZ@t+rP0}CK~<Jx%c?Rv
zY#&9e$fvBz%PE^h8Jp(QDkA0ixAN5BJhy~Ix0b7V`+VF>;~$iKQvU_Ft)^bf{#-f6
z0?&%#+4@&`aZ>*>bR&|TYXkrCpVxnFc)jH<Y+`7H7g~|eB*`=ODTx)AFg_hzktE)M
zbq`Vd>ypHL@SV0UyjdsL+;sf@cTcXgy#M9MWw=Tb+*e(DeKR<!z<)_LaRaWl!aSy1
z4)TV0q3%ep+g7NSJS6MFWD_<w26f&Wcaf$Y7|)Gv=)s!lybpOTq(@t|z?;8caJ`x@
zf2^o%lDHQ&C<l{U(eH=8e_O16E>>f-(N1`6?_QT}-GBkrfT{&JdiYyO9{O%`dURP5
zpG<PKHqZ{-Hb=pXvN1Ts?j}d^;j~UxEoi7){N8s8A%qY@2qA<JLI@#*5JCtcgb+dq
Oy`4X(IPzrxC;$MZe$A8s

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.3.0.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.3.0.crate
index ff912271d82473be662447f912096679956cad09..d60abaaaf1fcb233e4d848e393166c4a7a92351d 100644
GIT binary patch
literal 419
zcmV;U0bKqciwFP!000001MSnxYTPgs24JuK6vF$Ft+720r3nd>Ar#UqvK?lj*eAA#
zbwQE?d80xeV_vLsl7YE2ErcPZ^n4p%^xH?)k)^1uo_Ha_PBNO)ERBvX3#zglkE|-Q
z?c+woikz`LtBO42SU$~*h?K{l%Avt|ZV8F*Em!r!`?)`j|D)tV|L5GcntCn!W7QZN
zJS&QK`(I6qz5bVMRz)N`)&zd%Kkxt2@cMzbu!^A(UT8&5NixmYNfIkAVZ7SfB1!xR
z*1e@(wo4N8!FSraaA%p^a?|nkb5E|cT>o(7YuHK>+_|p3z8f4>;Lnm(+<>dCFq?9h
zgS;bNs3+3vwiT)+Z^gRsunChJL!I}=eM!>}jORu-bnBhzMIY)~NOz`cfj6&TaJ#s;
z{Jx;FN#b7Apd3u@MgKB%{cXOunJ>n)(N1`6pFUP?*?<AnfT{&Jy8T;89?sqB7fZMI
zkgK(UcHp+z2{X#ZaL7JRcJg7h_TF01(6{(+?<s^3LI@#*5JCtcgb+dqA%qY@2qE-&
NegZOygi!z}007<J(|!N|

literal 413
zcmV;O0b>3iiwFP!000001MSnxO2jY}0MM@c6`}h{-&RCL83z&6h1+2kvAwpz=0TDQ
z{*mBk_+zF%U}k)Q42pQqCTVk%o93p1*G5eo7hom{&1n(`8<%)hS&nU5mC17ZDoBeg
zPphoV)2$#$^DM6dQf_`LZw=NlLr8FCn5?fqj{9o-j}mX{KV_y>)G5(#D#uvht)lp_
z{#912)xY%JfFzsRz-Rt>{pXt1*Q|v_2n}~!NwP<xEJ?ScP%;7I)6x}*!W%I5CF<$A
zL?Ihor;PP)=FtVy9ecR%$*~d-cb4q>t0cf4sM@K^!BPosKU#zh*xGRYn(k;2m&9>-
zOM2C|T(;yTS?4Efzqv7}b57fxIPO3@rd30i){GB(pVvUVvQ-1DdH#Zn+2PUIjEW`-
zdtQUIFuCIW)6nV3bap<SjnP_J?v%OxUbcAy8e{{q=3wdaZzY)T+r>Law>HVvN<%v^
z)2sxiG#!I|?9F5)-k;Xmss;6R3xE4gA%qY@2qA<JLI@#*5JCtcgb+dqp`Y^znN1*$
H04M+eKQhW8

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.3.1.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.3.1.crate
index fcbe1422ea5bc2945a2459bb17297e42c53af1d8..25339c58200171fa3db9cdb90ca06fb4f5f0c964 100644
GIT binary patch
literal 416
zcmV;R0bl+fiwFP!000001MSn@O2aS|0N`EsDMIg;Hnw#lD#{!ps2ARjUBvdR4K_a{
zso)z4K87!5Iwx%N4`e9f`EJrSr#YuNsT8HvLoX!QVM;T~lEK!c!Kf(uk&TLUb$m5o
zdB#|lj`A$Tv1}&{q}YBbZ*|UdOUPhuxhiizj{9o<kCJctKjya9)N9#pD@Whp-8}!W
z|D$ZP{{`C({ZF@5z-Rt>|EGqRx4ec~1eNeYD{@HUos{jxk>V2iyOkpnM|WV|GHP*L
z;)r*?(bk10)A*X3hCe;F<V?$_2S*OVQIg<}b?Nm@=codI6wjgxTxo@QO?TSK8{&n!
zC#|k)p=z>B)`XW$D6S7`yf<z?N#@!)+JN!g=&D~ZSuUQ~(8^@3SPQ&*`G)Js$?4^U
z$|{apQG#+XoQrlbb#XqPT#YAvw9!s@ZSTLEZ(4x?Re>r6IJ)|4Ngl4<?49k~c*&L8
zK;3a$tpzj6Xu3#nH(ZMkJGQZEPDAga-#%9eA%qY@2qA<JLI@#*5JCtcgb+gLXFLO9
Kq5+TqC;$Mf<I3Ow

delta 414
zcmV;P0b%~21Em9hABzY80000000ZsQ&1&N?5CCAG{S?CY$X0CErEE)=G%SVPJ@guK
zQ0<8=YW*O|g}zZ?AEPg}ayEq|{eu#=l+JgNZH+Z!jVwiN^~4Jac9PMYvNSrlEU3zI
z99dOn+vB5%71Nwm(=umAQO5FVkwv6Dd?`;2&T~shw6a`()i*E4y*2+y$tV4vaocL@
zwd@a-V{GtIQM}myD&Oya$*R!*>`(=~=8N}#VR(JRTUf@>2rslErzDwX>^O-PmoVOK
z9g!ry1?x6Z>*JEdeDIyNE<9NzSKM^`;ZIL~X!&sO$hUBmB)D^3dwo4Ps=%Km%eVno
zTVWp4T@3Pnns}k^NUz&gsFrM!b>U?biW`GE?~OZ2)0K9Pc3?a=x)~QtH;bn>v@+c(
z)&g(-eZ$rK`^B$0l}!@&q6Xz)vJ(Az>gVNb{(Ckbqm6dLYkT+Je2WGQs0LImz|rkr
zOY(5-md|Y8-b=352HJt!W+#|YMzeK-+sRIR*s;9{RSOz=7k~7*LX&_26bt{yUoYAv
IJOC&F03ztkcK`qY

diff --git a/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.4.0.crate b/swh/loader/package/crates/tests/data/https_static.crates.io/crates_micro-timer_micro-timer-0.4.0.crate
index 1b50fa5b14b54d89b9be70bab3bb8eacb29050c0..6f30efba4df066d516a3182b5671d731e5935bac 100644
GIT binary patch
literal 419
zcmV;U0bKqciwFP!000001MSn@YTGav2H+g~DumBtTeaPcv5k#1jKQ{po`xK#_7_{!
z`azNly`#Y{qc=9WOCf326-w9`d!G~8)@w=DCo9=FGY?Xcn-^J?75VJsifB>SQ_B~1
zvEM$J@v7v!EEZK6bG$r@4XaPTlw+e0!Z9}6I-#5Er*qHs|D)7V{g=XZCJROlr<!9b
z@Of1|S^q`(xc)UikM%E3wSeFG&+EUoqPZ3wZ4zo_kVdn&EIljut2EI<(R8<WMAGDj
zoZqGHk4u_}G4#gy_+*`43fqgj?*sd2)ZI7F-o&G%$iFvDFju3`G=+ERCTYnxPTGg*
zE=G05g4DNcFkL5g$99wT@nstqH--8Ttbd*7TjPD!lMTX}b{a6>4W7GL%KTuljzs(O
z8!lHLF21ZX)uzcH8`7TUTRGf!eg3pueO<1mv^8D^<8EI%-?}ABx+UF6^4b2cr9`~;
zn_q0-qnCVREOn!B?LnAld<w_x&HNxAckF1?Si9tJpDO?W000000000000000008(i
NegF|zL+t=4004?i&ZPhV

literal 417
zcmV;S0bc$eiwFP!000001MSn@O2aS|0N`HtDMIhpHd|{&RFrLqpk8=8b`jgNHmvy}
zNd@0X@G*QbQ=PCme?W#Jp6@1YbDDFSlL}rMHE>*j8KiL*r%AALsaNEAKhh#k*T+`@
z&4wdd4D%7)3sO27W@$k3-IubhvyK@;f`wtSTz(w))%+hNw)#J2rdF|2qTN-FzQKE0
z_F?~v(cAv#wDA2;cU8b={(1jrnw3jd!#sqFJFX--BGE9V`%x&Ffc|dnh(zHn7`uvk
zJ}yznI@c&;{gYXA&2+;aA6jyz#N)jshyExDuqUc?>ZY@?1a}<G!wPI^xPDDH>BJ3j
zT;7pZ)iswjStV=y%i0&$2Q|)VdypgxWo_Jmc1)|PUocrMo*3WCWTRLEta|x|>*?v_
zavF;&3R_-+v@lrk_Ic{!d_27xPy1-CEO*M>eK+5%0u8bPS#q%P`mZIJzqa#tw(reL
zwp1GGj+tsBn8vgZ_OZ8vjd;IfTdVrsg};5S5JCtcgb+dqA%qY@2qA<JLI@#*(9d`R
LTp(vb04M+eb_d4~

diff --git a/swh/loader/package/crates/tests/test_crates.py b/swh/loader/package/crates/tests/test_crates.py
index 1ff76f72..2335d01b 100644
--- a/swh/loader/package/crates/tests/test_crates.py
+++ b/swh/loader/package/crates/tests/test_crates.py
@@ -2,112 +2,45 @@
 # See the AUTHORS file at the top-level directory of this distribution
 # License: GNU General Public License version 3, or any later version
 # See top-level LICENSE file for more information
+import json
+from pathlib import Path
+
 import pytest
 
+from swh.loader.package import __version__
 from swh.loader.package.crates.loader import CratesLoader
 from swh.loader.tests import assert_last_visit_matches, check_snapshot, get_stats
 from swh.model.hashutil import hash_to_bytes
 from swh.model.model import (
+    MetadataAuthority,
+    MetadataAuthorityType,
+    MetadataFetcher,
     ObjectType,
     Person,
+    RawExtrinsicMetadata,
     Release,
     Snapshot,
     SnapshotBranch,
     TargetType,
     TimestampWithTimezone,
 )
+from swh.model.swhids import CoreSWHID, ExtendedObjectType, ExtendedSWHID
+from swh.model.swhids import ObjectType as OType
+from swh.storage.interface import PagedResult
 
-CRATES_EXTRA = [
-    {
-        "url": "https://crates.io/api/v1/crates/hg-core",
-        "artifacts": [
-            {
-                "checksums": {
-                    "sha256": "48a45b46c2a8c38348adb1205b13c3c5eb0174e0c0fec52cc88e9fb1de14c54d",  # noqa: B950
-                },
-                "filename": "hg-core-0.0.1.crate",
-                "url": "https://static.crates.io/crates/hg-core/hg-core-0.0.1.crate",
-                "version": "0.0.1",
-            },
-        ],
-    },
-    {
-        "url": "https://crates.io/api/v1/crates/micro-timer",
-        "artifacts": [
-            {
-                "checksums": {
-                    "sha256": "69ad8fd116f8af0298ae4e83e587b1600af12709022471e25581c3aeb1da77ce",  # noqa: B950
-                },
-                "filename": "micro-timer-0.1.0.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.0.crate",
-                "version": "0.1.0",
-            },
-            {
-                "checksums": {
-                    "sha256": "7b3f65fe0e109daad8d47e1938c9b5f9353efacd86bbe7ff013f84ae7ca758bf",  # noqa: B950
-                },
-                "filename": "micro-timer-0.1.1.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.1.crate",
-                "version": "0.1.1",
-            },
-            {
-                "checksums": {
-                    "sha256": "16439fea388f712c1df7737ceb8f784d407844624b4796faf1e1bf8bbaa97445",  # noqa: B950
-                },
-                "filename": "micro-timer-0.1.2.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.1.2.crate",
-                "version": "0.1.2",
-            },
-            {
-                "checksums": {
-                    "sha256": "336b4c0f071d16674747faa4643d742cc096fec2bf8cf01bb1a98d984bedcaf1",  # noqa: B950
-                },
-                "filename": "micro-timer-0.2.0.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.2.0.crate",
-                "version": "0.2.0",
-            },
-            {
-                "checksums": {
-                    "sha256": "987429cd6162a80ed5ff44fc790f5090b1c6d617ac73a2e272965ed91201d79b",  # noqa: B950
-                },
-                "filename": "micro-timer-0.2.1.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.2.1.crate",
-                "version": "0.2.1",
-            },
-            {
-                "checksums": {
-                    "sha256": "25b31d6cb9112984323d05d7a353f272ae5d7a307074f9ab9b25c00121b8c947",  # noqa: B950
-                },
-                "filename": "micro-timer-0.3.0.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.3.0.crate",
-                "version": "0.3.0",
-            },
-            {
-                "checksums": {
-                    "sha256": "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c",  # noqa: B950
-                },
-                "filename": "micro-timer-0.3.1.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.3.1.crate",
-                "version": "0.3.1",
-            },
-            {
-                "checksums": {
-                    "sha256": "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405",  # noqa: B950
-                },
-                "filename": "micro-timer-0.4.0.crate",
-                "url": "https://static.crates.io/crates/micro-timer/micro-timer-0.4.0.crate",
-                "version": "0.4.0",
-            },
-        ],
-    },
-]
+
+@pytest.fixture
+def expected(datadir):
+    fp = datadir / Path("expected.json")
+    return json.loads(fp.read_bytes())
 
 
-def test_get_versions(requests_mock_datadir, swh_storage):
+def test_get_versions(requests_mock_datadir, swh_storage, expected):
     loader = CratesLoader(
         swh_storage,
-        url=CRATES_EXTRA[1]["url"],
-        artifacts=CRATES_EXTRA[1]["artifacts"],
+        url=expected[1]["url"],
+        artifacts=expected[1]["artifacts"],
+        crates_metadata=expected[1]["crates_metadata"],
     )
     assert loader.get_versions() == [
         "0.1.0",
@@ -121,11 +54,12 @@ def test_get_versions(requests_mock_datadir, swh_storage):
     ]
 
 
-def test_get_default_version(requests_mock_datadir, swh_storage):
+def test_get_default_version(requests_mock_datadir, swh_storage, expected):
     loader = CratesLoader(
         swh_storage,
-        url=CRATES_EXTRA[1]["url"],
-        artifacts=CRATES_EXTRA[1]["artifacts"],
+        url=expected[1]["url"],
+        artifacts=expected[1]["artifacts"],
+        crates_metadata=expected[1]["crates_metadata"],
     )
     assert loader.get_default_version() == "0.4.0"
 
@@ -137,9 +71,19 @@ def test_crate_invalid_origin_archive_not_found(swh_storage, requests_mock_datad
         url,
         artifacts=[
             {
+                "version": "0.0.1",
                 "filename": "nowhere-to-hide-0.0.1.crate",
                 "url": "https://nowhere-to-run/nowhere-to-hide-0.0.1.crate",
+                "checksums": {
+                    "sha256": "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405",  # noqa: B950
+                },
+            },
+        ],
+        crates_metadata=[
+            {
                 "version": "0.0.1",
+                "yanked": True,
+                "last_update": "1970-01-01T00:00:00.000000+00:00",
             },
         ],
     )
@@ -151,18 +95,21 @@ def test_crate_invalid_origin_archive_not_found(swh_storage, requests_mock_datad
         )
 
 
-def test_crates_loader_load_one_version(datadir, requests_mock_datadir, swh_storage):
+def test_crates_loader_load_one_version(
+    datadir, requests_mock_datadir, swh_storage, expected
+):
     loader = CratesLoader(
         swh_storage,
-        url=CRATES_EXTRA[0]["url"],
-        artifacts=CRATES_EXTRA[0]["artifacts"],
+        url=expected[0]["url"],
+        artifacts=expected[0]["artifacts"],
+        crates_metadata=expected[0]["crates_metadata"],
     )
     actual_load_status = loader.load()
     assert actual_load_status["status"] == "eventful"
     assert actual_load_status["snapshot_id"] is not None
 
-    expected_snapshot_id = "b3affb4949eb89b244f0e1d1fe235fc1d26bde76"
-    expected_release_id = "237c4cdd44a90e620795e5a07ebcc72bc82487f7"
+    expected_snapshot_id = "779fa94645a772fb9992c2bf80bf5e2ecfdb1b5c"
+    expected_release_id = "bb6f9b125867a8b4fa0b2febf890a317744e0140"
 
     assert expected_snapshot_id == actual_load_status["snapshot_id"]
 
@@ -195,9 +142,7 @@ def test_crates_loader_load_one_version(datadir, requests_mock_datadir, swh_stor
 
     assert swh_storage.release_get([hash_to_bytes(expected_release_id)])[0] == Release(
         name=b"0.0.1",
-        message=b"Synthetic release for Crate source package hg-core version 0.0.1\n\n"
-        b"Mercurial pure Rust core library, with no assumption "
-        b"on Python bindings (FFI)\n",
+        message=b"Synthetic release for Crate source package hg-core version 0.0.1\n",
         target=hash_to_bytes("674c3b0b54628d55b93a79dc7adf304efc01b371"),
         target_type=ObjectType.DIRECTORY,
         synthetic=True,
@@ -207,54 +152,59 @@ def test_crates_loader_load_one_version(datadir, requests_mock_datadir, swh_stor
     )
 
 
-def test_crates_loader_load_n_versions(datadir, requests_mock_datadir, swh_storage):
-    url = CRATES_EXTRA[1]["url"]
+def test_crates_loader_load_n_versions(
+    datadir, requests_mock_datadir_visits, swh_storage, expected
+):
+    url = expected[1]["url"]
+
     loader = CratesLoader(
         swh_storage,
-        url=CRATES_EXTRA[1]["url"],
-        artifacts=CRATES_EXTRA[1]["artifacts"],
+        url=url,
+        artifacts=expected[1]["artifacts"],
+        crates_metadata=expected[1]["crates_metadata"],
     )
-
     actual_load_status = loader.load()
+
     assert actual_load_status["status"] == "eventful"
     assert actual_load_status["snapshot_id"] is not None
 
-    expected_snapshot_id = "3f8ca5908a570fa32270b07a0946bcffa88babd5"
+    expected_snapshot_id = "d53bf516948733e422eaceb95dd1ef317e6f5df9"
+
     assert expected_snapshot_id == actual_load_status["snapshot_id"]
 
     expected_snapshot = Snapshot(
         id=hash_to_bytes(expected_snapshot_id),
         branches={
-            b"releases/0.4.0/micro-timer-0.4.0.crate": SnapshotBranch(
-                target=hash_to_bytes("b038a927244c852fb3794aecbebdc70f68ddf067"),
+            b"releases/0.1.0/micro-timer-0.1.0.crate": SnapshotBranch(
+                target=hash_to_bytes("bd8d093b4ad56ca7e49aa0f709d945483b831915"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.3.1/micro-timer-0.3.1.crate": SnapshotBranch(
-                target=hash_to_bytes("ea331a2ce755e6f0cd9d05c9be52accde68536c4"),
+            b"releases/0.1.1/micro-timer-0.1.1.crate": SnapshotBranch(
+                target=hash_to_bytes("a54bb00b38ae049ac4d7548a722bcd891811dbf6"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.3.0/micro-timer-0.3.0.crate": SnapshotBranch(
-                target=hash_to_bytes("7ea45f915ace083ed361bb12593625bf4cf1f5f2"),
+            b"releases/0.1.2/micro-timer-0.1.2.crate": SnapshotBranch(
+                target=hash_to_bytes("e234efbaef1999866d3b5c9a5676c42a5c4d1d3f"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.2.1/micro-timer-0.2.1.crate": SnapshotBranch(
-                target=hash_to_bytes("074f27605be8b759e5d7c638f026aac3709f58e5"),
+            b"releases/0.2.0/micro-timer-0.2.0.crate": SnapshotBranch(
+                target=hash_to_bytes("519bfdb898f91011eeb618d7d7aaa93097e57ab4"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.2.0/micro-timer-0.2.0.crate": SnapshotBranch(
-                target=hash_to_bytes("a1d642aaa54c5361f67e57adbd86e01f3a3276f8"),
+            b"releases/0.2.1/micro-timer-0.2.1.crate": SnapshotBranch(
+                target=hash_to_bytes("c1bdd5a5a0769c4fd10af92a43e66ce7a2394e8c"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.1.2/micro-timer-0.1.2.crate": SnapshotBranch(
-                target=hash_to_bytes("60f18ae067ce235bc60243bf5cdaaae474b11978"),
+            b"releases/0.3.0/micro-timer-0.3.0.crate": SnapshotBranch(
+                target=hash_to_bytes("d68b74713017c0b32a553f107d179be386bafdbc"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.1.1/micro-timer-0.1.1.crate": SnapshotBranch(
-                target=hash_to_bytes("fd6c55dfd016d58647a2d44b29a3fd4e3afa7671"),
+            b"releases/0.3.1/micro-timer-0.3.1.crate": SnapshotBranch(
+                target=hash_to_bytes("ee11442a44d32562aedf731932ae4a8a9cf23feb"),
                 target_type=TargetType.RELEASE,
             ),
-            b"releases/0.1.0/micro-timer-0.1.0.crate": SnapshotBranch(
-                target=hash_to_bytes("3e07559a4b366a397b1ca154e72753ce27223ca1"),
+            b"releases/0.4.0/micro-timer-0.4.0.crate": SnapshotBranch(
+                target=hash_to_bytes("0018b25f87d0838f6bef3e94b3000043bc9c938d"),
                 target_type=TargetType.RELEASE,
             ),
             b"HEAD": SnapshotBranch(
@@ -285,3 +235,269 @@ def test_crates_loader_load_n_versions(datadir, requests_mock_datadir, swh_stora
         type="crates",
         snapshot=expected_snapshot.id,
     )
+
+
+def test_crates_loader_load_multiple_visits_no_changes(
+    datadir, requests_mock_datadir_visits, requests_mock_datadir, swh_storage, expected
+):
+
+    url = expected[0]["url"]
+    loader = CratesLoader(
+        swh_storage,
+        url=url,
+        artifacts=expected[0]["artifacts"],
+        crates_metadata=expected[0]["crates_metadata"],
+    )
+
+    visit_1_actual_load_status = loader.load()
+    assert visit_1_actual_load_status["status"] == "eventful"
+    assert visit_1_actual_load_status["snapshot_id"] is not None
+
+    expected_snapshot_id = "779fa94645a772fb9992c2bf80bf5e2ecfdb1b5c"
+    expected_release_id = "bb6f9b125867a8b4fa0b2febf890a317744e0140"
+
+    assert expected_snapshot_id == visit_1_actual_load_status["snapshot_id"]
+
+    expected_snapshot = Snapshot(
+        id=hash_to_bytes(visit_1_actual_load_status["snapshot_id"]),
+        branches={
+            b"releases/0.0.1/hg-core-0.0.1.crate": SnapshotBranch(
+                target=hash_to_bytes(expected_release_id),
+                target_type=TargetType.RELEASE,
+            ),
+            b"HEAD": SnapshotBranch(
+                target=b"releases/0.0.1/hg-core-0.0.1.crate",
+                target_type=TargetType.ALIAS,
+            ),
+        },
+    )
+    check_snapshot(expected_snapshot, swh_storage)
+
+    assert_last_visit_matches(
+        swh_storage, url, status="full", type="crates", snapshot=expected_snapshot.id
+    )
+
+    stats = get_stats(swh_storage)
+    assert {
+        "content": 1,
+        "directory": 2,
+        "origin": 1,
+        "origin_visit": 1,
+        "release": 1,
+        "revision": 0,
+        "skipped_content": 0,
+        "snapshot": 1,
+    } == stats
+
+    assert swh_storage.release_get([hash_to_bytes(expected_release_id)])[0] == Release(
+        name=b"0.0.1",
+        message=b"Synthetic release for Crate source package hg-core version 0.0.1\n",
+        target=hash_to_bytes("674c3b0b54628d55b93a79dc7adf304efc01b371"),
+        target_type=ObjectType.DIRECTORY,
+        synthetic=True,
+        author=Person.from_fullname(b"Georges Racinet <georges.racinet@octobus.net>"),
+        date=TimestampWithTimezone.from_iso8601("2019-04-16T18:48:11.404457+00:00"),
+        id=hash_to_bytes(expected_release_id),
+    )
+
+    loader_2 = CratesLoader(
+        swh_storage,
+        url=url,
+        artifacts=expected[0]["artifacts"],
+        crates_metadata=expected[0]["crates_metadata"],
+    )
+    actual_load_status2 = loader_2.load()
+    assert actual_load_status2 == {
+        "status": "uneventful",
+        "snapshot_id": actual_load_status2["snapshot_id"],
+    }
+
+    visit_status2 = assert_last_visit_matches(
+        swh_storage, url, status="full", type="crates"
+    )
+
+    stats2 = get_stats(swh_storage)
+    expected_stats2 = stats.copy()
+    expected_stats2["origin_visit"] = 1 + 1
+    assert expected_stats2 == stats2
+
+    # same snapshot
+    assert visit_status2.snapshot == expected_snapshot.id
+
+
+def test_crates_loader_load_multiple_version_incremental(
+    datadir, requests_mock_datadir_visits, swh_storage, expected
+):
+
+    url = expected[1]["url"]
+    # one version in artifacts
+    artifacts_0 = [
+        artifact
+        for artifact in expected[1]["artifacts"]
+        if artifact["version"] in ["0.1.0"]
+    ]
+    crates_metadata_0 = [
+        artifact
+        for artifact in expected[1]["crates_metadata"]
+        if artifact["version"] in ["0.1.0"]
+    ]
+
+    # two versions in artifacts
+    artifacts_1 = [
+        artifact
+        for artifact in expected[1]["artifacts"]
+        if artifact["version"] in ["0.1.0", "0.1.1"]
+    ]
+    crates_metadata_1 = [
+        artifact
+        for artifact in expected[1]["crates_metadata"]
+        if artifact["version"] in ["0.1.0", "0.1.1"]
+    ]
+
+    # Visit 1
+    loader = CratesLoader(
+        swh_storage,
+        url=url,
+        artifacts=artifacts_0,
+        crates_metadata=crates_metadata_0,
+    )
+
+    visit1_actual_load_status = loader.load()
+    visit1_stats = get_stats(swh_storage)
+    expected_snapshot_id = hash_to_bytes("729d99ab2e68cde99c425251ac5725c478d686df")
+
+    assert visit1_actual_load_status == {
+        "status": "eventful",
+        "snapshot_id": expected_snapshot_id.hex(),
+    }
+
+    assert_last_visit_matches(
+        swh_storage, url, status="full", type="crates", snapshot=expected_snapshot_id
+    )
+
+    assert {
+        "content": 1,
+        "directory": 2,
+        "origin": 1,
+        "origin_visit": 1,
+        "release": 1,
+        "revision": 0,
+        "skipped_content": 0,
+        "snapshot": 1,
+    } == visit1_stats
+
+    # Visit 2
+    loader = CratesLoader(
+        swh_storage,
+        url=url,
+        artifacts=artifacts_1,
+        crates_metadata=crates_metadata_1,
+    )
+
+    visit2_actual_load_status = loader.load()
+    visit2_stats = get_stats(swh_storage)
+
+    assert visit2_actual_load_status["status"] == "eventful", visit2_actual_load_status
+    expected_snapshot_id2 = hash_to_bytes("eff31762ab65175192d6e1f0570d8b2e4176ff0d")
+    assert visit2_actual_load_status == {
+        "status": "eventful",
+        "snapshot_id": expected_snapshot_id2.hex(),
+    }
+
+    assert_last_visit_matches(
+        swh_storage, url, status="full", type="crates", snapshot=expected_snapshot_id2
+    )
+    expected_snapshot = Snapshot(
+        id=expected_snapshot_id2,
+        branches={
+            b"releases/0.1.1/micro-timer-0.1.1.crate": SnapshotBranch(
+                target=hash_to_bytes("a54bb00b38ae049ac4d7548a722bcd891811dbf6"),
+                target_type=TargetType.RELEASE,
+            ),
+            b"releases/0.1.0/micro-timer-0.1.0.crate": SnapshotBranch(
+                target=hash_to_bytes("bd8d093b4ad56ca7e49aa0f709d945483b831915"),
+                target_type=TargetType.RELEASE,
+            ),
+            b"HEAD": SnapshotBranch(
+                target=b"releases/0.1.1/micro-timer-0.1.1.crate",
+                target_type=TargetType.ALIAS,
+            ),
+        },
+    )
+
+    assert_last_visit_matches(
+        swh_storage, url, status="full", type="crates", snapshot=expected_snapshot.id
+    )
+
+    check_snapshot(expected_snapshot, swh_storage)
+
+    assert {
+        "content": 1 + 1,
+        "directory": 2 + 2,
+        "origin": 1,
+        "origin_visit": 1 + 1,
+        "release": 1 + 1,
+        "revision": 0,
+        "skipped_content": 0,
+        "snapshot": 1 + 1,
+    } == visit2_stats
+
+
+def test_crates_loader_raw_extrinsic_metadata(
+    datadir, requests_mock_datadir, swh_storage, expected
+):
+    loader = CratesLoader(
+        swh_storage,
+        url=expected[0]["url"],
+        artifacts=expected[0]["artifacts"],
+        crates_metadata=expected[0]["crates_metadata"],
+    )
+    actual_load_status = loader.load()
+    assert actual_load_status["status"] == "eventful"
+    assert actual_load_status["snapshot_id"] is not None
+
+    expected_release_id = "bb6f9b125867a8b4fa0b2febf890a317744e0140"
+
+    release = swh_storage.release_get([hash_to_bytes(expected_release_id)])[0]
+
+    release_swhid = CoreSWHID(
+        object_type=OType.RELEASE, object_id=hash_to_bytes(expected_release_id)
+    )
+    directory_swhid = ExtendedSWHID(
+        object_type=ExtendedObjectType.DIRECTORY, object_id=release.target
+    )
+    metadata_authority = MetadataAuthority(
+        type=MetadataAuthorityType.FORGE,
+        url="https://crates.io/",
+    )
+    expected_metadata = [
+        RawExtrinsicMetadata(
+            target=directory_swhid,
+            authority=metadata_authority,
+            fetcher=MetadataFetcher(
+                name="swh.loader.package.crates.loader.CratesLoader",
+                version=__version__,
+            ),
+            discovery_date=loader.visit_date,
+            format="crates-package-json",
+            metadata=json.dumps(
+                [
+                    {
+                        "version": "0.0.1",
+                        "yanked": False,
+                        "last_update": "2019-04-16T18:48:11.404457+00:00",
+                    }
+                ]
+            ).encode(),
+            origin=expected[0]["url"],
+            release=release_swhid,
+        ),
+    ]
+
+    assert swh_storage.raw_extrinsic_metadata_get(
+        directory_swhid,
+        metadata_authority,
+    ) == PagedResult(
+        next_page_token=None,
+        results=expected_metadata,
+    )
diff --git a/swh/loader/package/crates/tests/test_tasks.py b/swh/loader/package/crates/tests/test_tasks.py
index 2c1b459c..8aba6ab3 100644
--- a/swh/loader/package/crates/tests/test_tasks.py
+++ b/swh/loader/package/crates/tests/test_tasks.py
@@ -24,7 +24,23 @@ def crates_listed_origin(crates_lister):
         url="some-url/api/v1/crates/some-package",
         visit_type="crates",
         extra_loader_arguments={
-            "artifacts": [{"version": "0.0.1", "url": "some-package-0.0.1.crate"}],
+            "artifacts": [
+                {
+                    "version": "0.0.1",
+                    "filename": "some-package-0.0.1.crate",
+                    "url": "https://somewhere/some-package-0.0.1.crate",
+                    "checksums": {
+                        "sha256": "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405",  # noqa: B950
+                    },
+                },
+            ],
+            "crates_metadata": [
+                {
+                    "version": "0.0.1",
+                    "yanked": True,
+                    "last_update": "1970-01-01T00:00:00.000000+00:00",
+                },
+            ],
         },
     )
 
-- 
GitLab