Coverage for tests / unit / test_license_checker.py: 100%

84 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-19 22:28 +0000

1""" 

2Unit tests for src/license_checker.py 

3Covers: 

4 - Valid (non-expired) license 

5 - Expired license -> print + playsound called twice 

6 - License file does not exist -> FileNotFoundError 

7 - License file contains an invalid date -> ValueError 

8 - playsound is always mocked so no audio device is needed 

9""" 

10 

11import pytest 

12from datetime import datetime, timedelta 

13from unittest.mock import patch, mock_open, call 

14 

15from src.license_checker import check_license 

16 

17 

18# --------------------------------------------------------------------------- 

19# helpers 

20# --------------------------------------------------------------------------- 

21 

22def _date_str(days_offset: int) -> str: 

23 """Return a YYYY-MM-DD string relative to today.""" 

24 return (datetime.today() + timedelta(days=days_offset)).strftime("%Y-%m-%d") 

25 

26 

27# --------------------------------------------------------------------------- 

28# Valid license 

29# --------------------------------------------------------------------------- 

30 

31class TestLicenseValid: 

32 """License installed recently -> should be valid.""" 

33 

34 def test_returns_true_when_not_expired(self): 

35 installed = _date_str(-10) # installed 10 days ago -> 355 days left 

36 with patch("os.path.exists", return_value=True), \ 

37 patch("builtins.open", mock_open(read_data=installed)), \ 

38 patch("src.license_checker.playsound") as mock_sound: 

39 

40 result = check_license() 

41 

42 assert result is True 

43 mock_sound.assert_not_called() 

44 

45 def test_prints_days_remaining(self, capsys): 

46 installed = _date_str(-10) 

47 with patch("os.path.exists", return_value=True), \ 

48 patch("builtins.open", mock_open(read_data=installed)), \ 

49 patch("src.license_checker.playsound"): 

50 

51 check_license() 

52 

53 captured = capsys.readouterr() 

54 assert "License is valid" in captured.out 

55 assert "remaining" in captured.out 

56 

57 def test_valid_one_day_before_expiry(self): 

58 """Installed 364 days ago -> 1 day left -> still valid. 

59 

60 Note: datetime.today() carries the current wall-clock time, so an 

61 install date of exactly -365 days produces expiry_date at midnight 

62 *today*, which is already in the past. Using -364 guarantees there 

63 is always at least one day remaining regardless of time-of-day. 

64 """ 

65 installed = _date_str(-364) 

66 with patch("os.path.exists", return_value=True), \ 

67 patch("builtins.open", mock_open(read_data=installed)), \ 

68 patch("src.license_checker.playsound") as mock_sound: 

69 

70 result = check_license() 

71 

72 assert result is True 

73 mock_sound.assert_not_called() 

74 

75 

76# --------------------------------------------------------------------------- 

77# Expired license 

78# --------------------------------------------------------------------------- 

79 

80class TestLicenseExpired: 

81 """License installed more than `duration` days ago -> expired.""" 

82 

83 def test_returns_false_when_expired(self): 

84 installed = _date_str(-400) 

85 with patch("os.path.exists", return_value=True), \ 

86 patch("builtins.open", mock_open(read_data=installed)), \ 

87 patch("src.license_checker.playsound"): 

88 

89 result = check_license() 

90 

91 assert result is False 

92 

93 def test_prints_expiry_message(self, capsys): 

94 installed = _date_str(-400) 

95 with patch("os.path.exists", return_value=True), \ 

96 patch("builtins.open", mock_open(read_data=installed)), \ 

97 patch("src.license_checker.playsound"): 

98 

99 check_license() 

100 

101 captured = capsys.readouterr() 

102 assert "License expired" in captured.out 

103 

104 def test_playsound_called_exactly_twice(self): 

105 installed = _date_str(-400) 

106 with patch("os.path.exists", return_value=True), \ 

107 patch("builtins.open", mock_open(read_data=installed)), \ 

108 patch("src.license_checker.playsound") as mock_sound: 

109 

110 check_license() 

111 

112 assert mock_sound.call_count == 2 

113 

114 def test_playsound_called_with_correct_file(self): 

115 installed = _date_str(-400) 

116 with patch("os.path.exists", return_value=True), \ 

117 patch("builtins.open", mock_open(read_data=installed)), \ 

118 patch("src.license_checker.playsound") as mock_sound: 

119 

120 check_license() 

121 

122 mock_sound.assert_has_calls([call("alert.wav"), call("alert.wav")]) 

123 

124 def test_custom_duration_expired(self): 

125 """Custom shorter license (30 days) -> 31 days old -> expired.""" 

126 installed = _date_str(-31) 

127 with patch("os.path.exists", return_value=True), \ 

128 patch("builtins.open", mock_open(read_data=installed)), \ 

129 patch("src.license_checker.playsound") as mock_sound: 

130 

131 result = check_license(license_duration_days=30) 

132 

133 assert result is False 

134 assert mock_sound.call_count == 2 

135 

136 def test_custom_duration_valid(self): 

137 """Custom longer license (730 days) -> 400 days old -> still valid.""" 

138 installed = _date_str(-400) 

139 with patch("os.path.exists", return_value=True), \ 

140 patch("builtins.open", mock_open(read_data=installed)), \ 

141 patch("src.license_checker.playsound") as mock_sound: 

142 

143 result = check_license(license_duration_days=730) 

144 

145 assert result is True 

146 mock_sound.assert_not_called() 

147 

148 

149# --------------------------------------------------------------------------- 

150# File not found 

151# --------------------------------------------------------------------------- 

152 

153class TestLicenseFileNotFound: 

154 """License file is missing -> FileNotFoundError must be raised.""" 

155 

156 def test_raises_file_not_found(self): 

157 with patch("os.path.exists", return_value=False): 

158 with pytest.raises(FileNotFoundError): 

159 check_license() 

160 

161 def test_error_message_contains_filename(self): 

162 custom_path = "custom/path/license.txt" 

163 with patch("os.path.exists", return_value=False): 

164 with pytest.raises(FileNotFoundError, match="custom/path/license.txt"): 

165 check_license(license_file=custom_path) 

166 

167 def test_playsound_not_called_when_file_missing(self): 

168 with patch("os.path.exists", return_value=False), \ 

169 patch("src.license_checker.playsound") as mock_sound: 

170 

171 with pytest.raises(FileNotFoundError): 

172 check_license() 

173 

174 mock_sound.assert_not_called() 

175 

176 

177# --------------------------------------------------------------------------- 

178# Bad date format inside the file 

179# --------------------------------------------------------------------------- 

180 

181class TestLicenseBadDateFormat: 

182 """License file contains an un-parseable date -> ValueError must be raised.""" 

183 

184 def test_raises_value_error_on_bad_date(self): 

185 with patch("os.path.exists", return_value=True), \ 

186 patch("builtins.open", mock_open(read_data="not-a-date")): 

187 with pytest.raises(ValueError, match="Invalid date format"): 

188 check_license() 

189 

190 def test_raises_value_error_on_wrong_format(self): 

191 with patch("os.path.exists", return_value=True), \ 

192 patch("builtins.open", mock_open(read_data="17/03/2026")): 

193 with pytest.raises(ValueError): 

194 check_license() 

195