Coverage for python/lsst/daf/butler/tests/server.py: 8%
59 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-07 11:02 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-07 11:02 +0000
1import os
2from collections.abc import Iterator
3from contextlib import contextmanager
4from dataclasses import dataclass
5from tempfile import TemporaryDirectory
7from fastapi.testclient import TestClient
8from lsst.daf.butler import Butler, Config, LabeledButlerFactory
9from lsst.daf.butler.remote_butler import RemoteButler, RemoteButlerFactory
10from lsst.daf.butler.remote_butler.server import create_app
11from lsst.daf.butler.remote_butler.server._dependencies import butler_factory_dependency
12from lsst.resources.s3utils import clean_test_environment_for_s3, getS3Client
14from ..direct_butler import DirectButler
15from .hybrid_butler import HybridButler
16from .server_utils import add_auth_header_check_middleware
18try:
19 # moto v5
20 from moto import mock_aws # type: ignore
21except ImportError:
22 # moto v4 and earlier
23 from moto import mock_s3 as mock_aws # type: ignore
25__all__ = ("create_test_server", "TestServerInstance", "TEST_REPOSITORY_NAME")
28TEST_REPOSITORY_NAME = "testrepo"
31@dataclass(frozen=True)
32class TestServerInstance:
33 """Butler instances and other data associated with a temporary server
34 instance.
35 """
37 config_file_path: str
38 """Path to the Butler config file used by the server."""
39 client: TestClient
40 """HTTPX client connected to the temporary server."""
41 remote_butler: RemoteButler
42 """`RemoteButler` connected to the temporary server."""
43 remote_butler_without_error_propagation: RemoteButler
44 """`RemoteButler` connected to the temporary server.
46 By default, the TestClient instance raises any unhandled exceptions
47 from the server as if they had originated in the client to ease debugging.
48 However, this can make it appear that error propagation is working
49 correctly when in a real deployment the server exception would cause a 500
50 Internal Server Error. This instance of the butler is set up so that any
51 unhandled server exceptions do return a 500 status code."""
52 direct_butler: Butler
53 """`DirectButler` instance connected to the same repository as the
54 temporary server.
55 """
56 hybrid_butler: HybridButler
57 """`HybridButler` instance connected to the temporary server."""
60@contextmanager
61def create_test_server(test_directory: str) -> Iterator[TestServerInstance]:
62 """Create a temporary Butler server instance for testing.
64 Parameters
65 ----------
66 test_directory : `str`
67 Path to the ``tests/`` directory at the root of the repository,
68 containing Butler test configuration files.
70 Returns
71 -------
72 instance : `TestServerInstance`
73 Object containing Butler instances connected to the server and
74 associated information.
75 """
76 # Set up a mock S3 environment using Moto. Moto also monkeypatches the
77 # `requests` library so that any HTTP requests to presigned S3 URLs get
78 # redirected to the mocked S3.
79 # Note that all files are stored in memory.
80 with clean_test_environment_for_s3():
81 with mock_aws():
82 base_config_path = os.path.join(test_directory, "config/basic/server.yaml")
83 # Create S3 buckets used for the datastore in server.yaml.
84 for bucket in ["mutable-bucket", "immutable-bucket"]:
85 getS3Client().create_bucket(Bucket=bucket)
87 with TemporaryDirectory() as root:
88 Butler.makeRepo(root, config=Config(base_config_path), forceConfigRoot=False)
89 config_file_path = os.path.join(root, "butler.yaml")
91 app = create_app()
92 add_auth_header_check_middleware(app)
93 # Override the server's Butler initialization to point at our
94 # test repo
95 server_butler_factory = LabeledButlerFactory({TEST_REPOSITORY_NAME: config_file_path})
96 app.dependency_overrides[butler_factory_dependency] = lambda: server_butler_factory
98 client = TestClient(app)
99 client_without_error_propagation = TestClient(app, raise_server_exceptions=False)
101 remote_butler = _make_remote_butler(client)
102 remote_butler_without_error_propagation = _make_remote_butler(
103 client_without_error_propagation
104 )
106 direct_butler = Butler.from_config(config_file_path, writeable=True)
107 assert isinstance(direct_butler, DirectButler)
108 hybrid_butler = HybridButler(remote_butler, direct_butler)
110 yield TestServerInstance(
111 config_file_path=config_file_path,
112 client=client,
113 direct_butler=direct_butler,
114 remote_butler=remote_butler,
115 remote_butler_without_error_propagation=remote_butler_without_error_propagation,
116 hybrid_butler=hybrid_butler,
117 )
120def _make_remote_butler(client: TestClient) -> RemoteButler:
121 remote_butler_factory = RemoteButlerFactory(
122 f"https://test.example/api/butler/repo/{TEST_REPOSITORY_NAME}", client
123 )
124 return remote_butler_factory.create_butler_for_access_token("fake-access-token")