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

1import os 

2from collections.abc import Iterator 

3from contextlib import contextmanager 

4from dataclasses import dataclass 

5from tempfile import TemporaryDirectory 

6 

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 

13 

14from ..direct_butler import DirectButler 

15from .hybrid_butler import HybridButler 

16from .server_utils import add_auth_header_check_middleware 

17 

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 

24 

25__all__ = ("create_test_server", "TestServerInstance", "TEST_REPOSITORY_NAME") 

26 

27 

28TEST_REPOSITORY_NAME = "testrepo" 

29 

30 

31@dataclass(frozen=True) 

32class TestServerInstance: 

33 """Butler instances and other data associated with a temporary server 

34 instance. 

35 """ 

36 

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. 

45 

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.""" 

58 

59 

60@contextmanager 

61def create_test_server(test_directory: str) -> Iterator[TestServerInstance]: 

62 """Create a temporary Butler server instance for testing. 

63 

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. 

69 

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) 

86 

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") 

90 

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 

97 

98 client = TestClient(app) 

99 client_without_error_propagation = TestClient(app, raise_server_exceptions=False) 

100 

101 remote_butler = _make_remote_butler(client) 

102 remote_butler_without_error_propagation = _make_remote_butler( 

103 client_without_error_propagation 

104 ) 

105 

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) 

109 

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 ) 

118 

119 

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")