Coverage for app/backend/src/tests/test_config.py: 100%

79 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-09 11:54 +0000

1import typing 

2 

3import pytest 

4 

5from couchers.config import Config 

6 

7 

8def _complete_config(dev: bool) -> Config: 

9 """Build a config object attribute populated with a valid, truthy value. 

10 

11 This mirrors what Config.load_from_env() produces when every env var is set, 

12 so Config.check() should succeed against it. 

13 """ 

14 cfg = Config() 

15 for var_name, var_type in Config.__annotations__.items(): 

16 if var_type is bool: 

17 setattr(cfg, var_name, True) 

18 elif var_type is int: 

19 setattr(cfg, var_name, 1) 

20 elif var_type is bytes: 

21 setattr(cfg, var_name, b"x") 

22 elif typing.get_origin(var_type) is typing.Literal: # type: ignore[comparison-overlap] 

23 setattr(cfg, var_name, typing.get_args(var_type)[0]) 

24 else: 

25 setattr(cfg, var_name, "x") 

26 

27 cfg.DEV = dev 

28 if not dev: 

29 # production invariants that aren't satisfiable by a generic truthy value 

30 cfg.BASE_URL = "https://example.com" 

31 cfg.ENABLE_EMAIL = True 

32 cfg.IN_TEST = False 

33 cfg.FEATURE_FLAGS_FILE_OVERRIDE_PATH = "" 

34 return cfg 

35 

36 

37def test_load_from_env() -> None: 

38 cfg = Config() 

39 assert not hasattr(cfg, "BASE_URL") 

40 cfg.load_from_env({"BASE_URL": "https://example.com"}) 

41 assert cfg.BASE_URL == "https://example.com" 

42 

43 

44def test_load_from_env_types() -> None: 

45 cfg = Config() 

46 

47 cfg.load_from_env({"IN_TEST": "1"}) 

48 assert cfg.IN_TEST is True 

49 with pytest.raises(ValueError): 

50 cfg.load_from_env({"IN_TEST": "not a bool"}) 

51 

52 cfg.load_from_env({"BACKGROUND_WORKER_COUNT": "42"}) 

53 assert cfg.BACKGROUND_WORKER_COUNT == 42 

54 with pytest.raises(ValueError): 

55 cfg.load_from_env({"BACKGROUND_WORKER_COUNT": "not an int"}) 

56 

57 cfg.load_from_env({"SECRET": bytes.hex(b"abc")}) 

58 assert cfg.SECRET == b"abc" 

59 with pytest.raises(ValueError): 

60 cfg.load_from_env({"SECRET": "not hex"}) 

61 

62 cfg.load_from_env({"ROLE": "worker"}) 

63 assert cfg.ROLE == "worker" 

64 with pytest.raises(ValueError): 

65 cfg.load_from_env({"ROLE": "not a valid role"}) 

66 

67 

68def test_getitem() -> None: 

69 cfg = Config() 

70 cfg.BASE_URL = "https://example.com" 

71 assert cfg.BASE_URL == "https://example.com" 

72 assert cfg["BASE_URL"] == "https://example.com" 

73 

74 

75def test_setitem() -> None: 

76 cfg = Config() 

77 

78 cfg["BASE_URL"] = "https://example.com" 

79 assert cfg.BASE_URL == "https://example.com" 

80 assert cfg["BASE_URL"] == "https://example.com" 

81 

82 with pytest.raises(KeyError): 

83 cfg["NOT_A_KEY"] = "value" 

84 

85 with pytest.raises(TypeError): 

86 cfg["BASE_URL"] = 123 

87 

88 

89def test_instances_state_are_independent() -> None: 

90 # Default values are declared at the class level, but should be copied to each instance. 

91 assert Config.IN_TEST is False 

92 

93 cfg1 = Config() 

94 cfg2 = Config() 

95 

96 assert cfg1.IN_TEST is False 

97 assert cfg2.IN_TEST is False 

98 

99 cfg1.IN_TEST = True 

100 

101 assert cfg1.IN_TEST is True 

102 assert cfg2.IN_TEST is False 

103 

104 

105def test_copy() -> None: 

106 cfg = Config() 

107 

108 cfg.BACKGROUND_WORKER_COUNT = 1 

109 copy1 = cfg.copy() 

110 cfg.BACKGROUND_WORKER_COUNT = 2 

111 copy2 = cfg.copy() 

112 

113 assert copy1.BACKGROUND_WORKER_COUNT == 1 

114 assert copy2.BACKGROUND_WORKER_COUNT == 2 

115 

116 

117@pytest.mark.parametrize("dev", [True, False]) 

118def test_check_config_only_references_known_keys(dev): 

119 """Config.check() must only access config keys that are declared as attributes. 

120 

121 A reference to a key that was removed from attributes (e.g. a toggle migrated to a feature 

122 flag) would raise KeyError at app boot but is invisible to the rest of the test suite, since 

123 Config.check() only runs in app.py's startup path. Exercising it here catches that. 

124 """ 

125 _complete_config(dev=dev).check()