@mp-consulting/homebridge-daikin-cloud 1.3.5 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSE +39 -1
  2. package/README.md +5 -3
  3. package/dist/src/accessories/air-conditioning-accessory.d.ts +2 -2
  4. package/dist/src/accessories/air-conditioning-accessory.d.ts.map +1 -1
  5. package/dist/src/accessories/air-conditioning-accessory.js.map +1 -1
  6. package/dist/src/accessories/altherma-accessory.d.ts +2 -2
  7. package/dist/src/accessories/altherma-accessory.d.ts.map +1 -1
  8. package/dist/src/accessories/altherma-accessory.js.map +1 -1
  9. package/dist/src/accessories/base-accessory.d.ts +6 -6
  10. package/dist/src/accessories/base-accessory.d.ts.map +1 -1
  11. package/dist/src/accessories/base-accessory.js +15 -15
  12. package/dist/src/accessories/base-accessory.js.map +1 -1
  13. package/dist/src/api/daikin-api.d.ts +26 -26
  14. package/dist/src/api/daikin-api.d.ts.map +1 -1
  15. package/dist/src/api/daikin-api.js +68 -42
  16. package/dist/src/api/daikin-api.js.map +1 -1
  17. package/dist/src/api/daikin-cloud.repository.d.ts.map +1 -1
  18. package/dist/src/api/daikin-cloud.repository.js +22 -14
  19. package/dist/src/api/daikin-cloud.repository.js.map +1 -1
  20. package/dist/src/api/daikin-controller.d.ts +41 -47
  21. package/dist/src/api/daikin-controller.d.ts.map +1 -1
  22. package/dist/src/api/daikin-controller.js +40 -64
  23. package/dist/src/api/daikin-controller.js.map +1 -1
  24. package/dist/src/api/daikin-device.d.ts +36 -31
  25. package/dist/src/api/daikin-device.d.ts.map +1 -1
  26. package/dist/src/api/daikin-device.js +45 -31
  27. package/dist/src/api/daikin-device.js.map +1 -1
  28. package/dist/src/api/daikin-mobile-oauth.d.ts +20 -20
  29. package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
  30. package/dist/src/api/daikin-mobile-oauth.js +49 -44
  31. package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
  32. package/dist/src/api/daikin-oauth.d.ts +32 -32
  33. package/dist/src/api/daikin-oauth.d.ts.map +1 -1
  34. package/dist/src/api/daikin-oauth.js +64 -56
  35. package/dist/src/api/daikin-oauth.js.map +1 -1
  36. package/dist/src/api/daikin-schemas.d.ts +476 -351
  37. package/dist/src/api/daikin-schemas.d.ts.map +1 -1
  38. package/dist/src/api/daikin-schemas.js +11 -42
  39. package/dist/src/api/daikin-schemas.js.map +1 -1
  40. package/dist/src/api/daikin-types.d.ts +5 -1
  41. package/dist/src/api/daikin-types.d.ts.map +1 -1
  42. package/dist/src/api/daikin-types.js.map +1 -1
  43. package/dist/src/api/daikin-websocket.d.ts +31 -32
  44. package/dist/src/api/daikin-websocket.d.ts.map +1 -1
  45. package/dist/src/api/daikin-websocket.js +55 -35
  46. package/dist/src/api/daikin-websocket.js.map +1 -1
  47. package/dist/src/api/index.d.ts +2 -1
  48. package/dist/src/api/index.d.ts.map +1 -1
  49. package/dist/src/api/index.js +3 -1
  50. package/dist/src/api/index.js.map +1 -1
  51. package/dist/src/api/token-storage.d.ts +21 -0
  52. package/dist/src/api/token-storage.d.ts.map +1 -0
  53. package/dist/src/api/token-storage.js +90 -0
  54. package/dist/src/api/token-storage.js.map +1 -0
  55. package/dist/src/config/config-manager.d.ts +33 -33
  56. package/dist/src/config/config-manager.d.ts.map +1 -1
  57. package/dist/src/config/config-manager.js +33 -33
  58. package/dist/src/config/config-manager.js.map +1 -1
  59. package/dist/src/constants/api.constants.d.ts +4 -0
  60. package/dist/src/constants/api.constants.d.ts.map +1 -1
  61. package/dist/src/constants/api.constants.js +5 -1
  62. package/dist/src/constants/api.constants.js.map +1 -1
  63. package/dist/src/constants/device.constants.d.ts +4 -0
  64. package/dist/src/constants/device.constants.d.ts.map +1 -1
  65. package/dist/src/constants/device.constants.js +5 -1
  66. package/dist/src/constants/device.constants.js.map +1 -1
  67. package/dist/src/device/accessory-factory.d.ts +10 -10
  68. package/dist/src/device/accessory-factory.d.ts.map +1 -1
  69. package/dist/src/device/accessory-factory.js +7 -7
  70. package/dist/src/device/accessory-factory.js.map +1 -1
  71. package/dist/src/device/capability-detector.d.ts +8 -8
  72. package/dist/src/device/capability-detector.d.ts.map +1 -1
  73. package/dist/src/device/capability-detector.js +6 -6
  74. package/dist/src/device/capability-detector.js.map +1 -1
  75. package/dist/src/device/capability-docs.d.ts +1 -9
  76. package/dist/src/device/capability-docs.d.ts.map +1 -1
  77. package/dist/src/device/capability-docs.js +19 -73
  78. package/dist/src/device/capability-docs.js.map +1 -1
  79. package/dist/src/device/profiles/device-profile.d.ts +1 -1
  80. package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
  81. package/dist/src/device/profiles/device-profile.js +4 -4
  82. package/dist/src/device/profiles/device-profile.js.map +1 -1
  83. package/dist/src/features/base-feature.d.ts +2 -2
  84. package/dist/src/features/base-feature.d.ts.map +1 -1
  85. package/dist/src/features/base-feature.js +2 -3
  86. package/dist/src/features/base-feature.js.map +1 -1
  87. package/dist/src/features/feature-manager.d.ts +8 -16
  88. package/dist/src/features/feature-manager.d.ts.map +1 -1
  89. package/dist/src/features/feature-manager.js +5 -17
  90. package/dist/src/features/feature-manager.js.map +1 -1
  91. package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
  92. package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
  93. package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
  94. package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
  95. package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
  96. package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
  97. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
  98. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
  99. package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
  100. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
  101. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
  102. package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
  103. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
  104. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
  105. package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
  106. package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
  107. package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
  108. package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
  109. package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
  110. package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
  111. package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
  112. package/dist/src/index.d.ts +1 -1
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js.map +1 -1
  115. package/dist/src/platform.d.ts +11 -8
  116. package/dist/src/platform.d.ts.map +1 -1
  117. package/dist/src/platform.js +64 -15
  118. package/dist/src/platform.js.map +1 -1
  119. package/dist/src/services/climate-control.service.d.ts +8 -2
  120. package/dist/src/services/climate-control.service.d.ts.map +1 -1
  121. package/dist/src/services/climate-control.service.js +59 -58
  122. package/dist/src/services/climate-control.service.js.map +1 -1
  123. package/dist/src/services/hot-water-tank.service.d.ts +6 -2
  124. package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
  125. package/dist/src/services/hot-water-tank.service.js +33 -31
  126. package/dist/src/services/hot-water-tank.service.js.map +1 -1
  127. package/dist/src/types/daikin-enums.js +12 -12
  128. package/dist/src/types/daikin-enums.js.map +1 -1
  129. package/dist/src/types/device-capabilities.d.ts +1 -1
  130. package/dist/src/types/device-capabilities.d.ts.map +1 -1
  131. package/dist/src/utils/log-context.d.ts +23 -23
  132. package/dist/src/utils/log-context.d.ts.map +1 -1
  133. package/dist/src/utils/log-context.js +28 -28
  134. package/dist/src/utils/log-context.js.map +1 -1
  135. package/dist/src/utils/strings.d.ts.map +1 -1
  136. package/dist/src/utils/strings.js.map +1 -1
  137. package/dist/src/utils/update-mapper.d.ts +16 -16
  138. package/dist/src/utils/update-mapper.d.ts.map +1 -1
  139. package/dist/src/utils/update-mapper.js +14 -14
  140. package/dist/src/utils/update-mapper.js.map +1 -1
  141. package/homebridge-ui/public/index.html +2 -2
  142. package/homebridge-ui/public/script.js +957 -898
  143. package/homebridge-ui/server.js +746 -678
  144. package/package.json +29 -27
  145. package/.claude/settings.json +0 -3
  146. package/.claude/settings.local.json +0 -29
  147. package/CHANGELOG.md +0 -103
  148. package/CLAUDE.md +0 -269
  149. package/config.md +0 -2
  150. package/dist/src/api/daikin-device-tracker.d.ts +0 -97
  151. package/dist/src/api/daikin-device-tracker.d.ts.map +0 -1
  152. package/dist/src/api/daikin-device-tracker.js +0 -136
  153. package/dist/src/api/daikin-device-tracker.js.map +0 -1
  154. package/dist/src/api/http-interceptor.d.ts +0 -99
  155. package/dist/src/api/http-interceptor.d.ts.map +0 -1
  156. package/dist/src/api/http-interceptor.js +0 -177
  157. package/dist/src/api/http-interceptor.js.map +0 -1
  158. package/dist/src/di/service-container.d.ts +0 -92
  159. package/dist/src/di/service-container.d.ts.map +0 -1
  160. package/dist/src/di/service-container.js +0 -156
  161. package/dist/src/di/service-container.js.map +0 -1
  162. package/dist/src/features/feature-registry.d.ts +0 -100
  163. package/dist/src/features/feature-registry.d.ts.map +0 -1
  164. package/dist/src/features/feature-registry.js +0 -142
  165. package/dist/src/features/feature-registry.js.map +0 -1
  166. package/dist/src/services/service-factory.d.ts +0 -46
  167. package/dist/src/services/service-factory.d.ts.map +0 -1
  168. package/dist/src/services/service-factory.js +0 -72
  169. package/dist/src/services/service-factory.js.map +0 -1
  170. package/dist/src/utils/error-handler.d.ts +0 -101
  171. package/dist/src/utils/error-handler.d.ts.map +0 -1
  172. package/dist/src/utils/error-handler.js +0 -251
  173. package/dist/src/utils/error-handler.js.map +0 -1
  174. package/dist/src/utils/retry.d.ts +0 -42
  175. package/dist/src/utils/retry.d.ts.map +0 -1
  176. package/dist/src/utils/retry.js +0 -70
  177. package/dist/src/utils/retry.js.map +0 -1
  178. package/docs/ARCHITECTURE.md +0 -645
  179. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  180. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  181. package/docs/NEXT_STEPS.md +0 -368
  182. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  183. package/docs/TROUBLESHOOTING.md +0 -475
  184. package/docs/api-response-for-BRP069A8x.json +0 -520
  185. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  186. package/docs/api-response-for-BRP069C4x.json +0 -916
  187. package/docs/api-response-for-altherma.json +0 -759
  188. package/docs/api-response-for-altherma2.json +0 -2735
  189. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  190. package/docs/cr-insance-altherma-id-0.json +0 -834
  191. package/docs/mock-air-to-air-dx23.json +0 -759
  192. package/docs/mock-air-to-air-dx4.json +0 -1134
  193. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  194. package/docs/mock-airpurifier.json +0 -450
  195. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  196. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  197. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  198. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  199. package/images/fan-speed.jpeg +0 -0
  200. package/images/homekit-controls.jpeg +0 -0
  201. package/images/homekit-settings.jpeg +0 -0
  202. package/images/swing-mode.png +0 -0
  203. package/jest.config.ts +0 -13
  204. package/test/fixtures/altherma-crSense-2.ts +0 -834
  205. package/test/fixtures/altherma-fraction.ts +0 -718
  206. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  207. package/test/fixtures/altherma-heat-pump.ts +0 -757
  208. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  209. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  210. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  211. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  212. package/test/fixtures/dx23-airco-2.ts +0 -343
  213. package/test/fixtures/dx23-airco.ts +0 -518
  214. package/test/fixtures/dx4-airco.ts +0 -914
  215. package/test/fixtures/unknown-jan.ts +0 -488
  216. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  217. package/test/helpers/test-isolation.ts +0 -228
  218. package/test/integration/air-conditioning.test.ts +0 -410
  219. package/test/integration/altherma.test.ts +0 -289
  220. package/test/integration/platform.test.ts +0 -118
  221. package/test/mocks/index.ts +0 -27
  222. package/test/test-gigya-auth.js +0 -443
  223. package/test/test-mobile-oauth.js +0 -175
  224. package/test/test-websocket-mobile.js +0 -123
  225. package/test/test-websocket.js +0 -116
  226. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  227. package/test/unit/api/daikin-api.test.ts +0 -384
  228. package/test/unit/api/daikin-oauth.test.ts +0 -214
  229. package/test/unit/api/daikinCloud.test.ts +0 -12
  230. package/test/unit/config/config-manager.test.ts +0 -271
  231. package/test/unit/device/daikin-device.test.ts +0 -79
  232. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  233. package/test/unit/utils/error-handler.test.ts +0 -274
  234. package/test/unit/utils/log-context.test.ts +0 -271
@@ -1,274 +0,0 @@
1
- /**
2
- * ErrorHandler Tests
3
- */
4
-
5
- import {ErrorHandler, ErrorCategory, ErrorSeverity} from '../../../src/utils/error-handler';
6
- import {Logger} from 'homebridge';
7
-
8
- describe('ErrorHandler', () => {
9
- let mockLogger: Logger;
10
- let handler: ErrorHandler;
11
-
12
- beforeEach(() => {
13
- mockLogger = {
14
- debug: jest.fn(),
15
- info: jest.fn(),
16
- warn: jest.fn(),
17
- error: jest.fn(),
18
- log: jest.fn(),
19
- } as unknown as Logger;
20
-
21
- handler = new ErrorHandler(mockLogger, 'TestContext');
22
- });
23
-
24
- describe('categorize', () => {
25
- it('should categorize authentication errors', () => {
26
- const error = new Error('Unauthorized token expired');
27
- expect(handler.categorize(error)).toBe(ErrorCategory.AUTHENTICATION);
28
- });
29
-
30
- it('should categorize rate limit errors', () => {
31
- const error = new Error('Rate limit exceeded');
32
- expect(handler.categorize(error)).toBe(ErrorCategory.RATE_LIMIT);
33
- });
34
-
35
- it('should categorize network errors', () => {
36
- const error = new Error('ECONNRESET connection failed');
37
- expect(handler.categorize(error)).toBe(ErrorCategory.NETWORK);
38
- });
39
-
40
- it('should categorize websocket errors', () => {
41
- const error = new Error('ws: connection closed');
42
- expect(handler.categorize(error)).toBe(ErrorCategory.WEBSOCKET);
43
- });
44
-
45
- it('should categorize validation errors', () => {
46
- const error = new Error('Invalid configuration value');
47
- expect(handler.categorize(error)).toBe(ErrorCategory.VALIDATION);
48
- });
49
-
50
- it('should categorize device errors', () => {
51
- const error = new Error('Device not responding');
52
- expect(handler.categorize(error)).toBe(ErrorCategory.DEVICE);
53
- });
54
-
55
- it('should categorize unknown errors', () => {
56
- const error = new Error('Something went wrong');
57
- expect(handler.categorize(error)).toBe(ErrorCategory.UNKNOWN);
58
- });
59
- });
60
-
61
- describe('determineSeverity', () => {
62
- it('should mark rate limit as warning', () => {
63
- const error = new Error('Rate limit');
64
- const severity = handler.determineSeverity(error, ErrorCategory.RATE_LIMIT);
65
- expect(severity).toBe(ErrorSeverity.WARNING);
66
- });
67
-
68
- it('should mark authentication as fatal', () => {
69
- const error = new Error('Auth failed');
70
- const severity = handler.determineSeverity(error, ErrorCategory.AUTHENTICATION);
71
- expect(severity).toBe(ErrorSeverity.FATAL);
72
- });
73
-
74
- it('should mark configuration as fatal', () => {
75
- const error = new Error('Config error');
76
- const severity = handler.determineSeverity(error, ErrorCategory.CONFIGURATION);
77
- expect(severity).toBe(ErrorSeverity.FATAL);
78
- });
79
-
80
- it('should mark network as warning', () => {
81
- const error = new Error('Network error');
82
- const severity = handler.determineSeverity(error, ErrorCategory.NETWORK);
83
- expect(severity).toBe(ErrorSeverity.WARNING);
84
- });
85
-
86
- it('should mark device as error', () => {
87
- const error = new Error('Device error');
88
- const severity = handler.determineSeverity(error, ErrorCategory.DEVICE);
89
- expect(severity).toBe(ErrorSeverity.ERROR);
90
- });
91
- });
92
-
93
- describe('isRetryable', () => {
94
- it('should mark network errors as retryable', () => {
95
- const error = new Error('ECONNRESET');
96
- expect(handler.isRetryable(error)).toBe(true);
97
- });
98
-
99
- it('should mark timeout errors as retryable', () => {
100
- const error = new Error('ETIMEDOUT');
101
- expect(handler.isRetryable(error)).toBe(true);
102
- });
103
-
104
- it('should mark gateway errors as retryable', () => {
105
- const error = new Error('Bad gateway');
106
- expect(handler.isRetryable(error)).toBe(true);
107
- });
108
-
109
- it('should mark rate limit as retryable', () => {
110
- const error = new Error('Rate limit exceeded');
111
- expect(handler.isRetryable(error)).toBe(true);
112
- });
113
-
114
- it('should not mark validation errors as retryable', () => {
115
- const error = new Error('Invalid input');
116
- expect(handler.isRetryable(error)).toBe(false);
117
- });
118
- });
119
-
120
- describe('handle', () => {
121
- it('should handle error and log with context', () => {
122
- const error = new Error('Test error');
123
- const context = {
124
- category: ErrorCategory.API,
125
- operation: 'testOperation',
126
- deviceId: 'device123',
127
- };
128
-
129
- const handled = handler.handle(error, context);
130
-
131
- expect(handled.message).toBe('Test error');
132
- expect(handled.context.category).toBe(ErrorCategory.API);
133
- expect(handled.context.operation).toBe('testOperation');
134
- expect(handled.context.deviceId).toBe('device123');
135
- expect(mockLogger.error).toHaveBeenCalled();
136
- });
137
-
138
- it('should auto-categorize when category not provided', () => {
139
- const error = new Error('Unauthorized');
140
- const handled = handler.handle(error);
141
-
142
- expect(handled.context.category).toBe(ErrorCategory.AUTHENTICATION);
143
- });
144
-
145
- it('should auto-determine severity', () => {
146
- const error = new Error('Rate limit');
147
- const handled = handler.handle(error);
148
-
149
- expect(handled.context.severity).toBe(ErrorSeverity.WARNING);
150
- });
151
-
152
- it('should detect retryability', () => {
153
- const error = new Error('ECONNRESET');
154
- const handled = handler.handle(error);
155
-
156
- expect(handled.context.retryable).toBe(true);
157
- });
158
-
159
- it('should record error in history', () => {
160
- const error = new Error('Test error');
161
- handler.handle(error);
162
-
163
- const history = handler.getRecentErrors(1);
164
- expect(history).toHaveLength(1);
165
- expect(history[0].message).toBe('Test error');
166
- });
167
- });
168
-
169
- describe('handleWithMessage', () => {
170
- it('should return user-friendly message for authentication', () => {
171
- const error = new Error('Token expired');
172
- const message = handler.handleWithMessage(error, {
173
- category: ErrorCategory.AUTHENTICATION,
174
- });
175
-
176
- expect(message).toContain('Authentication failed');
177
- });
178
-
179
- it('should return user-friendly message for rate limit', () => {
180
- const error = new Error('Too many requests');
181
- const message = handler.handleWithMessage(error, {
182
- category: ErrorCategory.RATE_LIMIT,
183
- });
184
-
185
- expect(message).toContain('rate limit');
186
- });
187
-
188
- it('should return user-friendly message for network', () => {
189
- const error = new Error('Connection failed');
190
- const message = handler.handleWithMessage(error, {
191
- category: ErrorCategory.NETWORK,
192
- });
193
-
194
- expect(message).toContain('Network connection');
195
- });
196
- });
197
-
198
- describe('getRecentErrors', () => {
199
- it('should return limited number of recent errors', () => {
200
- for (let i = 0; i < 10; i++) {
201
- handler.handle(new Error(`Error ${i}`));
202
- }
203
-
204
- const recent = handler.getRecentErrors(5);
205
- expect(recent).toHaveLength(5);
206
- });
207
-
208
- it('should return all errors when no limit specified', () => {
209
- for (let i = 0; i < 5; i++) {
210
- handler.handle(new Error(`Error ${i}`));
211
- }
212
-
213
- const all = handler.getRecentErrors();
214
- expect(all).toHaveLength(5);
215
- });
216
- });
217
-
218
- describe('getErrorsByCategory', () => {
219
- it('should filter errors by category', () => {
220
- handler.handle(new Error('Auth error'), {category: ErrorCategory.AUTHENTICATION});
221
- handler.handle(new Error('Network error'), {category: ErrorCategory.NETWORK});
222
- handler.handle(new Error('Another auth error'), {category: ErrorCategory.AUTHENTICATION});
223
-
224
- const authErrors = handler.getErrorsByCategory(ErrorCategory.AUTHENTICATION);
225
- expect(authErrors).toHaveLength(2);
226
- });
227
- });
228
-
229
- describe('clearHistory', () => {
230
- it('should clear all error history', () => {
231
- handler.handle(new Error('Error 1'));
232
- handler.handle(new Error('Error 2'));
233
- expect(handler.getRecentErrors()).toHaveLength(2);
234
-
235
- handler.clearHistory();
236
- expect(handler.getRecentErrors()).toHaveLength(0);
237
- });
238
- });
239
-
240
- describe('logging', () => {
241
- it('should log fatal errors with stack trace', () => {
242
- const error = new Error('Fatal error');
243
- error.stack = 'Stack trace here';
244
-
245
- handler.handle(error, {
246
- category: ErrorCategory.AUTHENTICATION,
247
- severity: ErrorSeverity.FATAL,
248
- });
249
-
250
- expect(mockLogger.error).toHaveBeenCalledTimes(2); // Message + stack
251
- });
252
-
253
- it('should log warnings with warn level', () => {
254
- handler.handle(new Error('Warning'), {
255
- severity: ErrorSeverity.WARNING,
256
- });
257
-
258
- expect(mockLogger.warn).toHaveBeenCalled();
259
- });
260
-
261
- it('should include context in log messages', () => {
262
- handler.handle(new Error('Test'), {
263
- category: ErrorCategory.DEVICE,
264
- deviceId: 'device123',
265
- operation: 'update',
266
- });
267
-
268
- const logCall = (mockLogger.error as jest.Mock).mock.calls[0][0];
269
- expect(logCall).toContain('DEVICE');
270
- expect(logCall).toContain('device123');
271
- expect(logCall).toContain('update');
272
- });
273
- });
274
- });
@@ -1,271 +0,0 @@
1
- /**
2
- * StructuredLogger Tests
3
- */
4
-
5
- import {
6
- StructuredLogger,
7
- LogLevel,
8
- LogCategory,
9
- createLogger,
10
- createCategoryLogger,
11
- createDeviceLogger,
12
- } from '../../../src/utils/log-context';
13
- import {Logger} from 'homebridge';
14
-
15
- describe('StructuredLogger', () => {
16
- let mockLogger: Logger;
17
- let logger: StructuredLogger;
18
-
19
- beforeEach(() => {
20
- mockLogger = {
21
- debug: jest.fn(),
22
- info: jest.fn(),
23
- warn: jest.fn(),
24
- error: jest.fn(),
25
- log: jest.fn(),
26
- } as unknown as Logger;
27
-
28
- logger = new StructuredLogger(mockLogger);
29
- });
30
-
31
- describe('basic logging', () => {
32
- it('should log debug messages', () => {
33
- logger.debug('Debug message');
34
- expect(mockLogger.debug).toHaveBeenCalledWith('Debug message');
35
- });
36
-
37
- it('should log info messages', () => {
38
- logger.info('Info message');
39
- expect(mockLogger.info).toHaveBeenCalledWith('Info message');
40
- });
41
-
42
- it('should log warn messages', () => {
43
- logger.warn('Warning message');
44
- expect(mockLogger.warn).toHaveBeenCalledWith('Warning message');
45
- });
46
-
47
- it('should log error messages', () => {
48
- logger.error('Error message');
49
- expect(mockLogger.error).toHaveBeenCalledWith('Error message');
50
- });
51
- });
52
-
53
- describe('context formatting', () => {
54
- it('should include category in log message', () => {
55
- logger.log(LogLevel.INFO, 'Test', {category: LogCategory.API});
56
- expect(mockLogger.info).toHaveBeenCalledWith('[API] Test');
57
- });
58
-
59
- it('should include device name in log message', () => {
60
- logger.log(LogLevel.INFO, 'Test', {deviceName: 'Living Room'});
61
- expect(mockLogger.info).toHaveBeenCalledWith('[Living Room] Test');
62
- });
63
-
64
- it('should include device ID when no name', () => {
65
- logger.log(LogLevel.INFO, 'Test', {deviceId: 'device123'});
66
- expect(mockLogger.info).toHaveBeenCalledWith('[Device:device123] Test');
67
- });
68
-
69
- it('should include operation in log message', () => {
70
- logger.log(LogLevel.INFO, 'Test', {operation: 'update'});
71
- expect(mockLogger.info).toHaveBeenCalledWith('(update) Test');
72
- });
73
-
74
- it('should include embedded ID', () => {
75
- logger.log(LogLevel.INFO, 'Test', {embeddedId: 'climateControl'});
76
- expect(mockLogger.info).toHaveBeenCalledWith('[climateControl] Test');
77
- });
78
-
79
- it('should include feature name', () => {
80
- logger.log(LogLevel.INFO, 'Test', {feature: 'PowerfulMode'});
81
- expect(mockLogger.info).toHaveBeenCalledWith('[Feature:PowerfulMode] Test');
82
- });
83
-
84
- it('should combine multiple context fields', () => {
85
- logger.log(LogLevel.INFO, 'Test', {
86
- category: LogCategory.DEVICE,
87
- deviceName: 'Kitchen',
88
- operation: 'setState',
89
- });
90
- expect(mockLogger.info).toHaveBeenCalledWith('[Device] [Kitchen] (setState) Test');
91
- });
92
-
93
- it('should append metadata as JSON', () => {
94
- logger.log(LogLevel.INFO, 'Test', {
95
- metadata: {temp: 25, mode: 'cool'},
96
- });
97
-
98
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
99
- expect(logCall).toContain('{"temp":25,"mode":"cool"}');
100
- });
101
- });
102
-
103
- describe('default context', () => {
104
- it('should merge default context with call context', () => {
105
- const loggerWithDefaults = new StructuredLogger(mockLogger, {
106
- category: LogCategory.PLATFORM,
107
- });
108
-
109
- loggerWithDefaults.log(LogLevel.INFO, 'Test', {deviceId: 'device123'});
110
-
111
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
112
- expect(logCall).toContain('[Platform]');
113
- expect(logCall).toContain('[Device:device123]');
114
- });
115
-
116
- it('should override default context with call context', () => {
117
- const loggerWithDefaults = new StructuredLogger(mockLogger, {
118
- category: LogCategory.PLATFORM,
119
- });
120
-
121
- loggerWithDefaults.log(LogLevel.INFO, 'Test', {
122
- category: LogCategory.API,
123
- });
124
-
125
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
126
- expect(logCall).toContain('[API]');
127
- expect(logCall).not.toContain('[Platform]');
128
- });
129
-
130
- it('should merge metadata objects', () => {
131
- const loggerWithDefaults = new StructuredLogger(mockLogger, {
132
- metadata: {defaultKey: 'defaultValue'},
133
- });
134
-
135
- loggerWithDefaults.log(LogLevel.INFO, 'Test', {
136
- metadata: {callKey: 'callValue'},
137
- });
138
-
139
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
140
- expect(logCall).toContain('defaultKey');
141
- expect(logCall).toContain('callKey');
142
- });
143
- });
144
-
145
- describe('child logger', () => {
146
- it('should create child logger with additional context', () => {
147
- const child = logger.child({category: LogCategory.DEVICE});
148
- child.info('Test message');
149
-
150
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
151
- expect(logCall).toContain('[Device]');
152
- });
153
-
154
- it('should inherit parent context', () => {
155
- const parent = new StructuredLogger(mockLogger, {
156
- category: LogCategory.PLATFORM,
157
- });
158
-
159
- const child = parent.child({deviceName: 'Kitchen'});
160
- child.info('Test');
161
-
162
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
163
- expect(logCall).toContain('[Platform]');
164
- expect(logCall).toContain('[Kitchen]');
165
- });
166
- });
167
-
168
- describe('log history', () => {
169
- it('should record log entries in history', () => {
170
- logger.info('Message 1');
171
- logger.warn('Message 2');
172
- logger.error('Message 3');
173
-
174
- const history = logger.getHistory();
175
- expect(history).toHaveLength(3);
176
- });
177
-
178
- it('should return limited history', () => {
179
- for (let i = 0; i < 10; i++) {
180
- logger.info(`Message ${i}`);
181
- }
182
-
183
- const history = logger.getHistory(5);
184
- expect(history).toHaveLength(5);
185
- });
186
-
187
- it('should include context in history entries', () => {
188
- logger.info('Test', {category: LogCategory.API, deviceId: 'device123'});
189
-
190
- const history = logger.getHistory();
191
- expect(history[0].message).toBe('Test');
192
- expect(history[0].context?.category).toBe(LogCategory.API);
193
- expect(history[0].context?.deviceId).toBe('device123');
194
- });
195
-
196
- it('should include timestamp in history entries', () => {
197
- const before = new Date();
198
- logger.info('Test');
199
- const after = new Date();
200
-
201
- const history = logger.getHistory();
202
- expect(history[0].timestamp.getTime()).toBeGreaterThanOrEqual(before.getTime());
203
- expect(history[0].timestamp.getTime()).toBeLessThanOrEqual(after.getTime());
204
- });
205
-
206
- it('should clear history', () => {
207
- logger.info('Message 1');
208
- logger.info('Message 2');
209
- expect(logger.getHistory()).toHaveLength(2);
210
-
211
- logger.clearHistory();
212
- expect(logger.getHistory()).toHaveLength(0);
213
- });
214
- });
215
-
216
- describe('helper functions', () => {
217
- it('createLogger should create StructuredLogger', () => {
218
- const newLogger = createLogger(mockLogger);
219
- expect(newLogger).toBeInstanceOf(StructuredLogger);
220
- });
221
-
222
- it('createLogger should accept default context', () => {
223
- const newLogger = createLogger(mockLogger, {category: LogCategory.API});
224
- newLogger.info('Test');
225
-
226
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
227
- expect(logCall).toContain('[API]');
228
- });
229
-
230
- it('createCategoryLogger should create logger with category', () => {
231
- const newLogger = createCategoryLogger(mockLogger, LogCategory.WEBSOCKET);
232
- newLogger.info('Test');
233
-
234
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
235
- expect(logCall).toContain('[WebSocket]');
236
- });
237
-
238
- it('createDeviceLogger should create logger with device context', () => {
239
- const newLogger = createDeviceLogger(mockLogger, 'device123', 'Kitchen AC');
240
- newLogger.info('Test');
241
-
242
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
243
- expect(logCall).toContain('[Device]');
244
- expect(logCall).toContain('[Kitchen AC]');
245
- });
246
- });
247
-
248
- describe('edge cases', () => {
249
- it('should handle empty message', () => {
250
- logger.info('');
251
- expect(mockLogger.info).toHaveBeenCalled();
252
- });
253
-
254
- it('should handle undefined context', () => {
255
- logger.info('Test', undefined);
256
- expect(mockLogger.info).toHaveBeenCalledWith('Test');
257
- });
258
-
259
- it('should handle empty metadata', () => {
260
- logger.info('Test', {metadata: {}});
261
-
262
- const logCall = (mockLogger.info as jest.Mock).mock.calls[0][0];
263
- expect(logCall).toBe('Test');
264
- });
265
-
266
- it('should handle special characters in messages', () => {
267
- logger.info('Test [brackets] (parens) {braces}');
268
- expect(mockLogger.info).toHaveBeenCalled();
269
- });
270
- });
271
- });