Coverage for tests/conftest.py: 99%

73 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-09 12:09 +0100

1from __future__ import annotations 

2 

3import os 

4from copy import copy 

5from functools import partial 

6from pathlib import Path 

7from typing import Any 

8from typing import Generator 

9from typing import IO 

10from typing import Mapping 

11from typing import Protocol 

12 

13import click 

14import pytest 

15import typer 

16from _pytest.logging import LogCaptureFixture 

17from loguru import logger 

18from pydantic import BaseModel 

19from typer.testing import CliRunner 

20from typer.testing import Result 

21 

22from harbor_cli.main import app as main_app # noreorder 

23 

24# We can't import these before main is imported, because of circular imports 

25from harbor_cli.config import HarborCLIConfig 

26from harbor_cli.format import OutputFormat 

27from harbor_cli import state 

28from ._utils import compact_renderables 

29 

30 

31runner = CliRunner() 

32 

33 

34@pytest.fixture(scope="session") 

35def app(): 

36 return main_app 

37 

38 

39@pytest.fixture(scope="session", autouse=True) 

40def dumb_terminal(): 

41 """Test in a dumb terminal, so that we don't get ANSI escape codes in the output.""" 

42 os.environ["TERM"] = "dumb" 

43 os.environ["NO_COLOR"] = "1" 

44 os.environ.pop("COLORTERM", None) 

45 os.environ.pop("FORCE_COLOR", None) 

46 

47 

48@pytest.fixture(scope="session") 

49def config() -> HarborCLIConfig: 

50 conf = HarborCLIConfig() 

51 # These are required to run commands 

52 conf.harbor.url = "https://harbor.example.com" 

53 conf.harbor.username = "admin" 

54 conf.harbor.secret = "password" 

55 return conf 

56 

57 

58@pytest.fixture() 

59def config_file(tmp_path: Path, config: HarborCLIConfig) -> Path: # type: ignore 

60 """Setup the CLI config for testing.""" 

61 conf_path = tmp_path / "config.toml" 

62 config.save(conf_path) 

63 yield conf_path 

64 

65 

66class PartialInvoker(Protocol): 

67 """Protocol for a partial function that invokes a CLI command.""" 

68 

69 def __call__( 

70 self, 

71 args: str | list[str] | None, 

72 input: bytes | str | IO | None = None, 

73 env: Mapping[str, str] | None = None, 

74 catch_exceptions: bool = True, 

75 color: bool = False, 

76 **extra: Any, 

77 ) -> Result: 

78 ... 

79 

80 

81@pytest.fixture 

82def invoke(app: typer.Typer, config_file: Path) -> PartialInvoker: 

83 """Partial function for invoking a CLI command with the app as the entrypoint, 

84 and the temp config as the config file.""" 

85 p = partial(runner.invoke, app, env={"HARBOR_CLI_CONFIG": str(config_file)}) 

86 return p 

87 

88 

89@pytest.fixture 

90def mock_ctx() -> typer.Context: 

91 """Create a mock context.""" 

92 return typer.Context(click.Command(name="mock")) 

93 

94 

95@pytest.fixture(name="output_format", scope="function", params=list(OutputFormat)) 

96def _output_format(request: pytest.FixtureRequest) -> OutputFormat: 

97 """Fixture for testing all output formats.""" 

98 return request.param 

99 

100 

101@pytest.fixture(scope="function", params=list(OutputFormat)) 

102def output_format_arg(output_format: OutputFormat) -> list[str]: 

103 """Parametrized fixture that returns the CLI argument for all output formats.""" 

104 return ["--format", output_format.value] 

105 

106 

107_ORIGINAL_STATE = copy(state.state) 

108 

109 

110@pytest.fixture(scope="function", autouse=True) 

111def revert_state() -> Generator[None, None, None]: 

112 """Reverts the global state back to its original value after the test is run.""" 

113 yield 

114 state.state = _ORIGINAL_STATE 

115 

116 

117@pytest.fixture(scope="function", params=compact_renderables) 

118def compact_table_renderable(request: pytest.FixtureRequest) -> BaseModel: 

119 """Fixture for testing compact table renderables that can be instantiated with no arguments.""" 

120 return request.param() 

121 

122 

123@pytest.fixture 

124def caplog(caplog: LogCaptureFixture): 

125 handler_id = logger.add(caplog.handler, format="{message}") 

126 yield caplog 

127 logger.remove(handler_id)