@mp-consulting/homebridge-daikin-cloud 1.3.6 → 1.3.8

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 (291) hide show
  1. package/LICENSE +39 -1
  2. package/README.md +3 -1
  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 +5 -5
  10. package/dist/src/accessories/base-accessory.d.ts.map +1 -1
  11. package/dist/src/accessories/base-accessory.js +7 -4
  12. package/dist/src/accessories/base-accessory.js.map +1 -1
  13. package/dist/src/api/daikin-api.d.ts +25 -25
  14. package/dist/src/api/daikin-api.d.ts.map +1 -1
  15. package/dist/src/api/daikin-api.js +41 -31
  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.map +1 -1
  19. package/dist/src/api/daikin-controller.d.ts +41 -42
  20. package/dist/src/api/daikin-controller.d.ts.map +1 -1
  21. package/dist/src/api/daikin-controller.js +39 -39
  22. package/dist/src/api/daikin-controller.js.map +1 -1
  23. package/dist/src/api/daikin-device.d.ts +33 -31
  24. package/dist/src/api/daikin-device.d.ts.map +1 -1
  25. package/dist/src/api/daikin-device.js +40 -29
  26. package/dist/src/api/daikin-device.js.map +1 -1
  27. package/dist/src/api/daikin-mobile-oauth.d.ts +16 -16
  28. package/dist/src/api/daikin-mobile-oauth.d.ts.map +1 -1
  29. package/dist/src/api/daikin-mobile-oauth.js +32 -22
  30. package/dist/src/api/daikin-mobile-oauth.js.map +1 -1
  31. package/dist/src/api/daikin-oauth.d.ts +29 -29
  32. package/dist/src/api/daikin-oauth.d.ts.map +1 -1
  33. package/dist/src/api/daikin-oauth.js +45 -35
  34. package/dist/src/api/daikin-oauth.js.map +1 -1
  35. package/dist/src/api/daikin-schemas.d.ts +4 -4
  36. package/dist/src/api/daikin-schemas.js +3 -3
  37. package/dist/src/api/daikin-schemas.js.map +1 -1
  38. package/dist/src/api/daikin-types.js.map +1 -1
  39. package/dist/src/api/daikin-websocket.d.ts +31 -32
  40. package/dist/src/api/daikin-websocket.d.ts.map +1 -1
  41. package/dist/src/api/daikin-websocket.js +30 -30
  42. package/dist/src/api/daikin-websocket.js.map +1 -1
  43. package/dist/src/api/index.d.ts +1 -1
  44. package/dist/src/api/index.d.ts.map +1 -1
  45. package/dist/src/api/index.js +2 -1
  46. package/dist/src/api/index.js.map +1 -1
  47. package/dist/src/api/token-storage.d.ts +1 -1
  48. package/dist/src/api/token-storage.d.ts.map +1 -1
  49. package/dist/src/api/token-storage.js +20 -11
  50. package/dist/src/api/token-storage.js.map +1 -1
  51. package/dist/src/config/config-manager.d.ts +33 -33
  52. package/dist/src/config/config-manager.d.ts.map +1 -1
  53. package/dist/src/config/config-manager.js +33 -33
  54. package/dist/src/config/config-manager.js.map +1 -1
  55. package/dist/src/constants/api.constants.js.map +1 -1
  56. package/dist/src/device/accessory-factory.d.ts +10 -10
  57. package/dist/src/device/accessory-factory.d.ts.map +1 -1
  58. package/dist/src/device/accessory-factory.js +6 -6
  59. package/dist/src/device/accessory-factory.js.map +1 -1
  60. package/dist/src/device/capability-detector.d.ts +8 -8
  61. package/dist/src/device/capability-detector.d.ts.map +1 -1
  62. package/dist/src/device/capability-detector.js +6 -6
  63. package/dist/src/device/capability-detector.js.map +1 -1
  64. package/dist/src/device/capability-docs.d.ts +1 -1
  65. package/dist/src/device/capability-docs.d.ts.map +1 -1
  66. package/dist/src/device/capability-docs.js +1 -2
  67. package/dist/src/device/capability-docs.js.map +1 -1
  68. package/dist/src/device/profiles/device-profile.d.ts +1 -1
  69. package/dist/src/device/profiles/device-profile.d.ts.map +1 -1
  70. package/dist/src/device/profiles/device-profile.js +4 -4
  71. package/dist/src/device/profiles/device-profile.js.map +1 -1
  72. package/dist/src/features/base-feature.d.ts +2 -2
  73. package/dist/src/features/base-feature.d.ts.map +1 -1
  74. package/dist/src/features/base-feature.js +2 -3
  75. package/dist/src/features/base-feature.js.map +1 -1
  76. package/dist/src/features/feature-manager.d.ts +8 -8
  77. package/dist/src/features/feature-manager.d.ts.map +1 -1
  78. package/dist/src/features/feature-manager.js +5 -5
  79. package/dist/src/features/feature-manager.js.map +1 -1
  80. package/dist/src/features/modes/dry-operation-mode.feature.d.ts +1 -1
  81. package/dist/src/features/modes/dry-operation-mode.feature.d.ts.map +1 -1
  82. package/dist/src/features/modes/dry-operation-mode.feature.js.map +1 -1
  83. package/dist/src/features/modes/econo-mode.feature.d.ts +1 -1
  84. package/dist/src/features/modes/econo-mode.feature.d.ts.map +1 -1
  85. package/dist/src/features/modes/econo-mode.feature.js.map +1 -1
  86. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts +1 -1
  87. package/dist/src/features/modes/fan-only-operation-mode.feature.d.ts.map +1 -1
  88. package/dist/src/features/modes/fan-only-operation-mode.feature.js.map +1 -1
  89. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts +1 -1
  90. package/dist/src/features/modes/indoor-silent-mode.feature.d.ts.map +1 -1
  91. package/dist/src/features/modes/indoor-silent-mode.feature.js.map +1 -1
  92. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts +1 -1
  93. package/dist/src/features/modes/outdoor-silent-mode.feature.d.ts.map +1 -1
  94. package/dist/src/features/modes/outdoor-silent-mode.feature.js.map +1 -1
  95. package/dist/src/features/modes/powerful-mode.feature.d.ts +1 -1
  96. package/dist/src/features/modes/powerful-mode.feature.d.ts.map +1 -1
  97. package/dist/src/features/modes/powerful-mode.feature.js.map +1 -1
  98. package/dist/src/features/modes/streamer-mode.feature.d.ts +1 -1
  99. package/dist/src/features/modes/streamer-mode.feature.d.ts.map +1 -1
  100. package/dist/src/features/modes/streamer-mode.feature.js.map +1 -1
  101. package/dist/src/index.d.ts +1 -1
  102. package/dist/src/index.d.ts.map +1 -1
  103. package/dist/src/index.js.map +1 -1
  104. package/dist/src/platform.d.ts +6 -5
  105. package/dist/src/platform.d.ts.map +1 -1
  106. package/dist/src/platform.js +2 -2
  107. package/dist/src/platform.js.map +1 -1
  108. package/dist/src/services/climate-control.service.d.ts +8 -2
  109. package/dist/src/services/climate-control.service.d.ts.map +1 -1
  110. package/dist/src/services/climate-control.service.js +53 -59
  111. package/dist/src/services/climate-control.service.js.map +1 -1
  112. package/dist/src/services/hot-water-tank.service.d.ts +6 -2
  113. package/dist/src/services/hot-water-tank.service.d.ts.map +1 -1
  114. package/dist/src/services/hot-water-tank.service.js +31 -34
  115. package/dist/src/services/hot-water-tank.service.js.map +1 -1
  116. package/dist/src/types/daikin-enums.js +12 -12
  117. package/dist/src/types/daikin-enums.js.map +1 -1
  118. package/dist/src/types/device-capabilities.d.ts +1 -1
  119. package/dist/src/types/device-capabilities.d.ts.map +1 -1
  120. package/dist/src/utils/log-context.d.ts +23 -23
  121. package/dist/src/utils/log-context.d.ts.map +1 -1
  122. package/dist/src/utils/log-context.js +28 -28
  123. package/dist/src/utils/log-context.js.map +1 -1
  124. package/dist/src/utils/strings.d.ts.map +1 -1
  125. package/dist/src/utils/strings.js.map +1 -1
  126. package/dist/src/utils/update-mapper.d.ts +16 -16
  127. package/dist/src/utils/update-mapper.d.ts.map +1 -1
  128. package/dist/src/utils/update-mapper.js +14 -14
  129. package/dist/src/utils/update-mapper.js.map +1 -1
  130. package/homebridge-ui/public/index.html +24 -24
  131. package/homebridge-ui/public/lib/kit.css +253 -0
  132. package/homebridge-ui/public/lib/kit.js +133 -0
  133. package/homebridge-ui/public/script.js +957 -898
  134. package/homebridge-ui/public/styles.css +0 -1
  135. package/homebridge-ui/server.js +739 -695
  136. package/package.json +30 -25
  137. package/.claude/settings.json +0 -3
  138. package/.claude/settings.local.json +0 -24
  139. package/CHANGELOG.md +0 -114
  140. package/CLAUDE.md +0 -269
  141. package/config.md +0 -2
  142. package/docs/ARCHITECTURE.md +0 -645
  143. package/docs/IMPLEMENTATION_GUIDE.md +0 -899
  144. package/docs/IMPROVEMENTS_SUMMARY.md +0 -415
  145. package/docs/NEXT_STEPS.md +0 -368
  146. package/docs/Screenshot 2024-07-04 at 18.41.28.png +0 -0
  147. package/docs/TROUBLESHOOTING.md +0 -475
  148. package/docs/api-response-for-BRP069A8x.json +0 -520
  149. package/docs/api-response-for-BRP069C4x-2.json +0 -881
  150. package/docs/api-response-for-BRP069C4x.json +0 -916
  151. package/docs/api-response-for-altherma.json +0 -759
  152. package/docs/api-response-for-altherma2.json +0 -2735
  153. package/docs/api-response-with-multiple-devices-incl-heatpump.json +0 -2544
  154. package/docs/cr-insance-altherma-id-0.json +0 -834
  155. package/docs/mock-air-to-air-dx23.json +0 -759
  156. package/docs/mock-air-to-air-dx4.json +0 -1134
  157. package/docs/mock-airpurifier-with-humidifier.json +0 -732
  158. package/docs/mock-airpurifier.json +0 -450
  159. package/docs/mock-altherma-air-to-water-lan.json +0 -845
  160. package/docs/mock-altherma-air-to-water-wlan.json +0 -845
  161. package/docs/mock-d2cnd-gas-boiler.json +0 -649
  162. package/docs/setpointmode-vs-controlmode-vs-setpoints-vs-sensorydata.txt +0 -6
  163. package/images/fan-speed.jpeg +0 -0
  164. package/images/homekit-controls.jpeg +0 -0
  165. package/images/homekit-settings.jpeg +0 -0
  166. package/images/swing-mode.png +0 -0
  167. package/jest.config.ts +0 -21
  168. package/test/fixtures/altherma-crSense-2.ts +0 -834
  169. package/test/fixtures/altherma-fraction.ts +0 -718
  170. package/test/fixtures/altherma-heat-pump-2.ts +0 -479
  171. package/test/fixtures/altherma-heat-pump.ts +0 -757
  172. package/test/fixtures/altherma-miladcerkic-off.ts +0 -524
  173. package/test/fixtures/altherma-miladcerkic.ts +0 -524
  174. package/test/fixtures/altherma-v1ckoeln.ts +0 -644
  175. package/test/fixtures/altherma-with-embedded-id-zero.ts +0 -834
  176. package/test/fixtures/dx23-airco-2.ts +0 -343
  177. package/test/fixtures/dx23-airco.ts +0 -518
  178. package/test/fixtures/dx4-airco.ts +0 -914
  179. package/test/fixtures/unknown-jan.ts +0 -488
  180. package/test/fixtures/unknown-kitchen-guests.ts +0 -488
  181. package/test/hbConfig/.daikin-mobile-tokenset +0 -8
  182. package/test/hbConfig/.uix-dashboard.json +0 -1
  183. package/test/hbConfig/.uix-secrets +0 -1
  184. package/test/hbConfig/accessories/.cachedAccessories.bak +0 -1
  185. package/test/hbConfig/accessories/cachedAccessories +0 -1
  186. package/test/hbConfig/accessories/uiAccessoriesLayout.json +0 -1
  187. package/test/hbConfig/auth.json +0 -10
  188. package/test/hbConfig/backups/config-backups/config.json.1767953686461 +0 -25
  189. package/test/hbConfig/backups/config-backups/config.json.1767953695236 +0 -29
  190. package/test/hbConfig/backups/config-backups/config.json.1767953814763 +0 -29
  191. package/test/hbConfig/backups/config-backups/config.json.1767953823101 +0 -29
  192. package/test/hbConfig/backups/config-backups/config.json.1767954822835 +0 -29
  193. package/test/hbConfig/backups/config-backups/config.json.1767954859218 +0 -29
  194. package/test/hbConfig/backups/config-backups/config.json.1767960145503 +0 -33
  195. package/test/hbConfig/backups/config-backups/config.json.1767960168068 +0 -44
  196. package/test/hbConfig/backups/config-backups/config.json.1767960170333 +0 -46
  197. package/test/hbConfig/backups/config-backups/config.json.1767960172731 +0 -44
  198. package/test/hbConfig/backups/config-backups/config.json.1767960179323 +0 -44
  199. package/test/hbConfig/backups/config-backups/config.json.1767960182114 +0 -44
  200. package/test/hbConfig/backups/config-backups/config.json.1767960189302 +0 -44
  201. package/test/hbConfig/backups/config-backups/config.json.1767960195194 +0 -44
  202. package/test/hbConfig/backups/config-backups/config.json.1767960197301 +0 -44
  203. package/test/hbConfig/backups/config-backups/config.json.1767960199151 +0 -44
  204. package/test/hbConfig/backups/config-backups/config.json.1767960199667 +0 -44
  205. package/test/hbConfig/backups/config-backups/config.json.1767960329839 +0 -44
  206. package/test/hbConfig/backups/config-backups/config.json.1767960334503 +0 -44
  207. package/test/hbConfig/backups/config-backups/config.json.1767960336208 +0 -44
  208. package/test/hbConfig/backups/config-backups/config.json.1767960338537 +0 -44
  209. package/test/hbConfig/backups/config-backups/config.json.1767963223953 +0 -44
  210. package/test/hbConfig/backups/config-backups/config.json.1767963241753 +0 -44
  211. package/test/hbConfig/backups/config-backups/config.json.1767963252785 +0 -44
  212. package/test/hbConfig/backups/config-backups/config.json.1767963463944 +0 -44
  213. package/test/hbConfig/backups/config-backups/config.json.1767963834475 +0 -44
  214. package/test/hbConfig/backups/config-backups/config.json.1767963838474 +0 -44
  215. package/test/hbConfig/backups/config-backups/config.json.1767963843066 +0 -44
  216. package/test/hbConfig/backups/config-backups/config.json.1767965217715 +0 -44
  217. package/test/hbConfig/backups/config-backups/config.json.1767965419624 +0 -25
  218. package/test/hbConfig/backups/config-backups/config.json.1767965870934 +0 -32
  219. package/test/hbConfig/backups/config-backups/config.json.1767977675045 +0 -32
  220. package/test/hbConfig/backups/config-backups/config.json.1767977677222 +0 -33
  221. package/test/hbConfig/backups/config-backups/config.json.1767977710226 +0 -33
  222. package/test/hbConfig/backups/config-backups/config.json.1767977741397 +0 -33
  223. package/test/hbConfig/backups/config-backups/config.json.1767977977093 +0 -35
  224. package/test/hbConfig/backups/config-backups/config.json.1767977981773 +0 -35
  225. package/test/hbConfig/backups/config-backups/config.json.1767977986514 +0 -35
  226. package/test/hbConfig/backups/config-backups/config.json.1767977991174 +0 -35
  227. package/test/hbConfig/backups/config-backups/config.json.1767979424487 +0 -35
  228. package/test/hbConfig/backups/config-backups/config.json.1767979424987 +0 -35
  229. package/test/hbConfig/backups/config-backups/config.json.1767979432646 +0 -47
  230. package/test/hbConfig/backups/config-backups/config.json.1767979433150 +0 -47
  231. package/test/hbConfig/backups/config-backups/config.json.1767979436933 +0 -47
  232. package/test/hbConfig/backups/config-backups/config.json.1767979437438 +0 -47
  233. package/test/hbConfig/backups/config-backups/config.json.1767979441676 +0 -47
  234. package/test/hbConfig/backups/config-backups/config.json.1767979442180 +0 -47
  235. package/test/hbConfig/backups/config-backups/config.json.1767979466735 +0 -47
  236. package/test/hbConfig/backups/config-backups/config.json.1767979903636 +0 -47
  237. package/test/hbConfig/backups/config-backups/config.json.1767979904135 +0 -47
  238. package/test/hbConfig/backups/config-backups/config.json.1767979906606 +0 -47
  239. package/test/hbConfig/backups/config-backups/config.json.1767979907108 +0 -47
  240. package/test/hbConfig/backups/config-backups/config.json.1767988702341 +0 -47
  241. package/test/hbConfig/backups/config-backups/config.json.1767988702837 +0 -47
  242. package/test/hbConfig/backups/config-backups/config.json.1767988713159 +0 -47
  243. package/test/hbConfig/backups/config-backups/config.json.1767988713664 +0 -47
  244. package/test/hbConfig/backups/config-backups/config.json.1767988918139 +0 -47
  245. package/test/hbConfig/backups/config-backups/config.json.1767988918639 +0 -47
  246. package/test/hbConfig/backups/config-backups/config.json.1767988921120 +0 -47
  247. package/test/hbConfig/backups/config-backups/config.json.1767988921624 +0 -47
  248. package/test/hbConfig/backups/config-backups/config.json.1767988930307 +0 -47
  249. package/test/hbConfig/backups/config-backups/config.json.1767988935070 +0 -47
  250. package/test/hbConfig/backups/config-backups/config.json.1767988935574 +0 -47
  251. package/test/hbConfig/backups/config-backups/config.json.1767989710262 +0 -47
  252. package/test/hbConfig/backups/config-backups/config.json.1767989710760 +0 -47
  253. package/test/hbConfig/backups/config-backups/config.json.1767989729668 +0 -47
  254. package/test/hbConfig/backups/config-backups/config.json.1767990295225 +0 -47
  255. package/test/hbConfig/backups/config-backups/config.json.1767990479921 +0 -47
  256. package/test/hbConfig/backups/config-backups/config.json.1767990481702 +0 -49
  257. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768010187391.tar.gz +0 -0
  258. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768096587387.tar.gz +0 -0
  259. package/test/hbConfig/backups/instance-backups/homebridge-backup-1E4A432551BA.1768182987404.tar.gz +0 -0
  260. package/test/hbConfig/config.json +0 -47
  261. package/test/hbConfig/daikin-cloud-certs/server.crt +0 -22
  262. package/test/hbConfig/daikin-cloud-certs/server.key +0 -28
  263. package/test/hbConfig/persist/AccessoryInfo.1E4A432551BA.json +0 -1
  264. package/test/hbConfig/persist/IdentifierCache.1E4A432551BA.json +0 -1
  265. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14758 +0 -1
  266. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14759 +0 -1
  267. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14760 +0 -1
  268. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14761 +0 -1
  269. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14762 +0 -1
  270. package/test/hbConfig/tmp/daikin_request/api.onecta.daikineurope.com_01-09-2026-23-29-52/request_14764 +0 -1
  271. package/test/helpers/test-isolation.ts +0 -228
  272. package/test/integration/air-conditioning.test.ts +0 -396
  273. package/test/integration/altherma.test.ts +0 -279
  274. package/test/integration/platform.test.ts +0 -118
  275. package/test/mobile-tokens.json +0 -8
  276. package/test/mocks/index.ts +0 -27
  277. package/test/test-gigya-auth.js +0 -443
  278. package/test/test-mobile-oauth.js +0 -175
  279. package/test/test-websocket-mobile.js +0 -123
  280. package/test/test-websocket.js +0 -116
  281. package/test/unit/api/__snapshots__/daikinCloud.test.ts.snap +0 -1320
  282. package/test/unit/api/daikin-api.test.ts +0 -442
  283. package/test/unit/api/daikin-cloud-repository.test.ts +0 -107
  284. package/test/unit/api/daikin-oauth.test.ts +0 -214
  285. package/test/unit/api/daikinCloud.test.ts +0 -12
  286. package/test/unit/api/token-storage.test.ts +0 -90
  287. package/test/unit/config/config-manager.test.ts +0 -271
  288. package/test/unit/device/daikin-device.test.ts +0 -73
  289. package/test/unit/services/hot-water-tank.service.test.ts +0 -123
  290. package/test/unit/utils/log-context.test.ts +0 -271
  291. package/test/unit/utils/update-mapper.test.ts +0 -404
@@ -1,214 +0,0 @@
1
- import {DaikinOAuth} from '../../../src/api/daikin-oauth';
2
- import * as fs from 'node:fs';
3
- import * as https from 'node:https';
4
-
5
- jest.mock('node:fs');
6
- jest.mock('node:https');
7
-
8
- describe('DaikinOAuth', () => {
9
- const mockConfig = {
10
- clientId: 'test-client-id',
11
- clientSecret: 'test-client-secret',
12
- callbackServerExternalAddress: '192.168.1.1',
13
- callbackServerPort: 8582,
14
- tokenFilePath: '/tmp/test-token.json',
15
- };
16
-
17
- beforeEach(() => {
18
- jest.clearAllMocks();
19
- (fs.existsSync as jest.Mock).mockReturnValue(false);
20
- });
21
-
22
- describe('getAccessToken', () => {
23
- it('should return the access token if not expired', async () => {
24
- const futureExpiry = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
25
- const tokenSet = {
26
- access_token: 'valid-token',
27
- refresh_token: 'refresh-token',
28
- token_type: 'Bearer',
29
- expires_at: futureExpiry,
30
- };
31
-
32
- (fs.existsSync as jest.Mock).mockReturnValue(true);
33
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
34
-
35
- const oauth = new DaikinOAuth(mockConfig);
36
- const token = await oauth.getAccessToken();
37
-
38
- expect(token).toBe('valid-token');
39
- });
40
-
41
- it('should refresh the token if expired', async () => {
42
- const pastExpiry = Math.floor(Date.now() / 1000) - 100; // 100 seconds ago
43
- const tokenSet = {
44
- access_token: 'expired-token',
45
- refresh_token: 'refresh-token',
46
- token_type: 'Bearer',
47
- expires_at: pastExpiry,
48
- };
49
-
50
- const newTokenSet = {
51
- access_token: 'new-token',
52
- refresh_token: 'new-refresh-token',
53
- token_type: 'Bearer',
54
- expires_in: 3600,
55
- };
56
-
57
- (fs.existsSync as jest.Mock).mockReturnValue(true);
58
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
59
-
60
- // Mock the HTTPS request for token refresh
61
- const mockResponse = {
62
- on: jest.fn((event, callback) => {
63
- if (event === 'data') {
64
- callback(JSON.stringify(newTokenSet));
65
- }
66
- if (event === 'end') {
67
- callback();
68
- }
69
- }),
70
- };
71
- const mockRequest = {
72
- on: jest.fn(),
73
- write: jest.fn(),
74
- end: jest.fn(),
75
- };
76
- (https.request as jest.Mock).mockImplementation((options, callback) => {
77
- callback(mockResponse);
78
- return mockRequest;
79
- });
80
-
81
- const oauth = new DaikinOAuth(mockConfig);
82
- const token = await oauth.getAccessToken();
83
-
84
- expect(token).toBe('new-token');
85
- expect(https.request).toHaveBeenCalled();
86
- });
87
-
88
- it('should refresh the token if about to expire (within 10 seconds)', async () => {
89
- const soonExpiry = Math.floor(Date.now() / 1000) + 5; // 5 seconds from now
90
- const tokenSet = {
91
- access_token: 'soon-expiring-token',
92
- refresh_token: 'refresh-token',
93
- token_type: 'Bearer',
94
- expires_at: soonExpiry,
95
- };
96
-
97
- const newTokenSet = {
98
- access_token: 'refreshed-token',
99
- refresh_token: 'new-refresh-token',
100
- token_type: 'Bearer',
101
- expires_in: 3600,
102
- };
103
-
104
- (fs.existsSync as jest.Mock).mockReturnValue(true);
105
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
106
-
107
- const mockResponse = {
108
- on: jest.fn((event, callback) => {
109
- if (event === 'data') {
110
- callback(JSON.stringify(newTokenSet));
111
- }
112
- if (event === 'end') {
113
- callback();
114
- }
115
- }),
116
- };
117
- const mockRequest = {
118
- on: jest.fn(),
119
- write: jest.fn(),
120
- end: jest.fn(),
121
- };
122
- (https.request as jest.Mock).mockImplementation((options, callback) => {
123
- callback(mockResponse);
124
- return mockRequest;
125
- });
126
-
127
- const oauth = new DaikinOAuth(mockConfig);
128
- const token = await oauth.getAccessToken();
129
-
130
- expect(token).toBe('refreshed-token');
131
- expect(https.request).toHaveBeenCalled();
132
- });
133
-
134
- it('should throw error if token expired and no refresh token', async () => {
135
- const pastExpiry = Math.floor(Date.now() / 1000) - 100;
136
- const tokenSet = {
137
- access_token: 'expired-token',
138
- token_type: 'Bearer',
139
- expires_at: pastExpiry,
140
- // No refresh_token
141
- };
142
-
143
- (fs.existsSync as jest.Mock).mockReturnValue(true);
144
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
145
-
146
- const oauth = new DaikinOAuth(mockConfig);
147
-
148
- await expect(oauth.getAccessToken()).rejects.toThrow(
149
- 'Token expired and no refresh token available. Please re-authenticate.',
150
- );
151
- });
152
-
153
- it('should throw error if not authenticated', async () => {
154
- const oauth = new DaikinOAuth(mockConfig);
155
-
156
- await expect(oauth.getAccessToken()).rejects.toThrow(
157
- 'Not authenticated. Please authenticate first.',
158
- );
159
- });
160
- });
161
-
162
- describe('isAuthenticated', () => {
163
- it('should return true if token set exists', () => {
164
- const tokenSet = {
165
- access_token: 'token',
166
- token_type: 'Bearer',
167
- };
168
-
169
- (fs.existsSync as jest.Mock).mockReturnValue(true);
170
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
171
-
172
- const oauth = new DaikinOAuth(mockConfig);
173
- expect(oauth.isAuthenticated()).toBe(true);
174
- });
175
-
176
- it('should return false if no token set', () => {
177
- const oauth = new DaikinOAuth(mockConfig);
178
- expect(oauth.isAuthenticated()).toBe(false);
179
- });
180
- });
181
-
182
- describe('getTokenExpiration', () => {
183
- it('should return expiration date', () => {
184
- const expiresAt = Math.floor(Date.now() / 1000) + 3600;
185
- const tokenSet = {
186
- access_token: 'token',
187
- token_type: 'Bearer',
188
- expires_at: expiresAt,
189
- };
190
-
191
- (fs.existsSync as jest.Mock).mockReturnValue(true);
192
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
193
-
194
- const oauth = new DaikinOAuth(mockConfig);
195
- const expiration = oauth.getTokenExpiration();
196
-
197
- expect(expiration).toBeInstanceOf(Date);
198
- expect(expiration?.getTime()).toBe(expiresAt * 1000);
199
- });
200
-
201
- it('should return null if no expiration', () => {
202
- const tokenSet = {
203
- access_token: 'token',
204
- token_type: 'Bearer',
205
- };
206
-
207
- (fs.existsSync as jest.Mock).mockReturnValue(true);
208
- (fs.readFileSync as jest.Mock).mockReturnValue(JSON.stringify(tokenSet));
209
-
210
- const oauth = new DaikinOAuth(mockConfig);
211
- expect(oauth.getTokenExpiration()).toBeNull();
212
- });
213
- });
214
- });
@@ -1,12 +0,0 @@
1
- import {DaikinCloudRepo} from '../../../src/api/daikin-cloud.repository';
2
- import {dx4Airco} from '../../fixtures/dx4-airco';
3
- import {dx23Airco} from '../../fixtures/dx23-airco';
4
- import {althermaHeatPump} from '../../fixtures/altherma-heat-pump';
5
-
6
- test.each<Array<string | any>>([
7
- ['dx4', dx4Airco],
8
- ['dx23', dx23Airco],
9
- ['altherma', althermaHeatPump],
10
- ])('Clean cloud device data for %s device', (name, deviceJson) => {
11
- expect(DaikinCloudRepo.maskSensitiveCloudDeviceData(deviceJson)).toMatchSnapshot();
12
- });
@@ -1,90 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import {loadTokenFromFile, saveTokenToFile, deleteTokenFile} from '../../../src/api/token-storage';
3
- import {TokenSet} from '../../../src/api/daikin-types';
4
-
5
- jest.mock('node:fs');
6
-
7
- const mockFs = fs as jest.Mocked<typeof fs>;
8
-
9
- describe('Token Storage', () => {
10
- const filePath = '/tmp/test-token.json';
11
- const tokenSet: TokenSet = {
12
- access_token: 'test-access-token',
13
- refresh_token: 'test-refresh-token',
14
- token_type: 'Bearer',
15
- expires_in: 3600,
16
- expires_at: Math.floor(Date.now() / 1000) + 3600,
17
- };
18
-
19
- beforeEach(() => {
20
- jest.clearAllMocks();
21
- });
22
-
23
- describe('loadTokenFromFile', () => {
24
- it('should return null if file does not exist', () => {
25
- mockFs.existsSync.mockReturnValue(false);
26
- expect(loadTokenFromFile(filePath)).toBeNull();
27
- });
28
-
29
- it('should return parsed token set from file', () => {
30
- mockFs.existsSync.mockReturnValue(true);
31
- mockFs.readFileSync.mockReturnValue(JSON.stringify(tokenSet));
32
- const result = loadTokenFromFile(filePath);
33
- expect(result).toEqual(tokenSet);
34
- });
35
-
36
- it('should return null on invalid JSON', () => {
37
- mockFs.existsSync.mockReturnValue(true);
38
- mockFs.readFileSync.mockReturnValue('not-json{{{');
39
- expect(loadTokenFromFile(filePath)).toBeNull();
40
- });
41
-
42
- it('should return null on invalid token structure', () => {
43
- mockFs.existsSync.mockReturnValue(true);
44
- // Missing required access_token and token_type
45
- mockFs.readFileSync.mockReturnValue(JSON.stringify({ foo: 'bar' }));
46
- expect(loadTokenFromFile(filePath)).toBeNull();
47
- });
48
-
49
- it('should return null if readFileSync throws', () => {
50
- mockFs.existsSync.mockReturnValue(true);
51
- mockFs.readFileSync.mockImplementation(() => {
52
- throw new Error('Permission denied');
53
- });
54
- expect(loadTokenFromFile(filePath)).toBeNull();
55
- });
56
- });
57
-
58
- describe('saveTokenToFile', () => {
59
- it('should write token set with restricted permissions', () => {
60
- saveTokenToFile(filePath, tokenSet);
61
- expect(mockFs.writeFileSync).toHaveBeenCalledWith(
62
- filePath,
63
- JSON.stringify(tokenSet, null, 2),
64
- { encoding: 'utf8', mode: 0o600 },
65
- );
66
- });
67
- });
68
-
69
- describe('deleteTokenFile', () => {
70
- it('should delete file if it exists', () => {
71
- mockFs.existsSync.mockReturnValue(true);
72
- deleteTokenFile(filePath);
73
- expect(mockFs.unlinkSync).toHaveBeenCalledWith(filePath);
74
- });
75
-
76
- it('should not throw if file does not exist', () => {
77
- mockFs.existsSync.mockReturnValue(false);
78
- expect(() => deleteTokenFile(filePath)).not.toThrow();
79
- expect(mockFs.unlinkSync).not.toHaveBeenCalled();
80
- });
81
-
82
- it('should not throw if unlinkSync fails', () => {
83
- mockFs.existsSync.mockReturnValue(true);
84
- mockFs.unlinkSync.mockImplementation(() => {
85
- throw new Error('Permission denied');
86
- });
87
- expect(() => deleteTokenFile(filePath)).not.toThrow();
88
- });
89
- });
90
- });
@@ -1,271 +0,0 @@
1
- /**
2
- * ConfigManager Tests
3
- */
4
-
5
- import {ConfigManager, PluginConfig} from '../../../src/config/config-manager';
6
-
7
- describe('ConfigManager', () => {
8
- describe('getAuthMode', () => {
9
- it('should return mobile_app when configured', () => {
10
- const config: PluginConfig = {
11
- platform: 'DaikinCloud',
12
- authMode: 'mobile_app',
13
- };
14
- const manager = new ConfigManager(config);
15
- expect(manager.getAuthMode()).toBe('mobile_app');
16
- });
17
-
18
- it('should default to developer_portal when not specified', () => {
19
- const config: PluginConfig = {
20
- platform: 'DaikinCloud',
21
- };
22
- const manager = new ConfigManager(config);
23
- expect(manager.getAuthMode()).toBe('developer_portal');
24
- });
25
- });
26
-
27
- describe('getDeveloperCredentials', () => {
28
- it('should return credentials when all fields present', () => {
29
- const config: PluginConfig = {
30
- platform: 'DaikinCloud',
31
- clientId: 'test-id',
32
- clientSecret: 'test-secret',
33
- callbackServerExternalAddress: 'example.com',
34
- callbackServerPort: 8582,
35
- };
36
- const manager = new ConfigManager(config);
37
- const creds = manager.getDeveloperCredentials();
38
-
39
- expect(creds).not.toBeNull();
40
- expect(creds?.clientId).toBe('test-id');
41
- expect(creds?.clientSecret).toBe('test-secret');
42
- expect(creds?.callbackServerExternalAddress).toBe('example.com');
43
- expect(creds?.callbackServerPort).toBe(8582);
44
- });
45
-
46
- it('should return null when fields missing', () => {
47
- const config: PluginConfig = {
48
- platform: 'DaikinCloud',
49
- clientId: 'test-id',
50
- };
51
- const manager = new ConfigManager(config);
52
- expect(manager.getDeveloperCredentials()).toBeNull();
53
- });
54
- });
55
-
56
- describe('getMobileCredentials', () => {
57
- it('should return credentials when present', () => {
58
- const config: PluginConfig = {
59
- platform: 'DaikinCloud',
60
- daikinEmail: 'test@example.com',
61
- daikinPassword: 'password123',
62
- };
63
- const manager = new ConfigManager(config);
64
- const creds = manager.getMobileCredentials();
65
-
66
- expect(creds).not.toBeNull();
67
- expect(creds?.email).toBe('test@example.com');
68
- expect(creds?.password).toBe('password123');
69
- });
70
-
71
- it('should return null when fields missing', () => {
72
- const config: PluginConfig = {
73
- platform: 'DaikinCloud',
74
- daikinEmail: 'test@example.com',
75
- };
76
- const manager = new ConfigManager(config);
77
- expect(manager.getMobileCredentials()).toBeNull();
78
- });
79
- });
80
-
81
- describe('validate', () => {
82
- it('should pass validation for valid developer portal config', () => {
83
- const config: PluginConfig = {
84
- platform: 'DaikinCloud',
85
- authMode: 'developer_portal',
86
- clientId: 'test-id',
87
- clientSecret: 'test-secret',
88
- callbackServerExternalAddress: '192.168.1.100',
89
- callbackServerPort: 8582,
90
- };
91
- const manager = new ConfigManager(config);
92
- const result = manager.validate();
93
-
94
- expect(result.valid).toBe(true);
95
- expect(result.errors).toHaveLength(0);
96
- });
97
-
98
- it('should fail validation for developer portal without clientId', () => {
99
- const config: PluginConfig = {
100
- platform: 'DaikinCloud',
101
- authMode: 'developer_portal',
102
- clientSecret: 'test-secret',
103
- callbackServerExternalAddress: '192.168.1.100',
104
- };
105
- const manager = new ConfigManager(config);
106
- const result = manager.validate();
107
-
108
- expect(result.valid).toBe(false);
109
- expect(result.errors).toContain('Client ID is required for Developer Portal mode');
110
- });
111
-
112
- it('should fail validation for localhost callback address', () => {
113
- const config: PluginConfig = {
114
- platform: 'DaikinCloud',
115
- authMode: 'developer_portal',
116
- clientId: 'test-id',
117
- clientSecret: 'test-secret',
118
- callbackServerExternalAddress: 'localhost',
119
- };
120
- const manager = new ConfigManager(config);
121
- const result = manager.validate();
122
-
123
- expect(result.valid).toBe(false);
124
- expect(result.errors).toContain('Callback address cannot be localhost. Use your external IP or domain.');
125
- });
126
-
127
- it('should fail validation for invalid port', () => {
128
- const config: PluginConfig = {
129
- platform: 'DaikinCloud',
130
- authMode: 'developer_portal',
131
- clientId: 'test-id',
132
- clientSecret: 'test-secret',
133
- callbackServerExternalAddress: '192.168.1.100',
134
- callbackServerPort: 99999,
135
- };
136
- const manager = new ConfigManager(config);
137
- const result = manager.validate();
138
-
139
- expect(result.valid).toBe(false);
140
- expect(result.errors.length).toBeGreaterThan(0);
141
- expect(result.errors.some(e => e.includes('port'))).toBe(true);
142
- });
143
-
144
- it('should warn about privileged port', () => {
145
- const config: PluginConfig = {
146
- platform: 'DaikinCloud',
147
- authMode: 'developer_portal',
148
- clientId: 'test-id',
149
- clientSecret: 'test-secret',
150
- callbackServerExternalAddress: '192.168.1.100',
151
- callbackServerPort: 443,
152
- };
153
- const manager = new ConfigManager(config);
154
- const result = manager.validate();
155
-
156
- expect(result.valid).toBe(true);
157
- expect(result.warnings).toContain('Port 443 is privileged (< 1024) and may require root permissions.');
158
- });
159
-
160
- it('should pass validation for valid mobile app config', () => {
161
- const config: PluginConfig = {
162
- platform: 'DaikinCloud',
163
- authMode: 'mobile_app',
164
- daikinEmail: 'test@example.com',
165
- daikinPassword: 'password123',
166
- };
167
- const manager = new ConfigManager(config);
168
- const result = manager.validate();
169
-
170
- expect(result.valid).toBe(true);
171
- expect(result.errors).toHaveLength(0);
172
- });
173
-
174
- it('should fail validation for mobile app without email', () => {
175
- const config: PluginConfig = {
176
- platform: 'DaikinCloud',
177
- authMode: 'mobile_app',
178
- daikinPassword: 'password123',
179
- };
180
- const manager = new ConfigManager(config);
181
- const result = manager.validate();
182
-
183
- expect(result.valid).toBe(false);
184
- expect(result.errors).toContain('Email is required for Mobile App mode');
185
- });
186
-
187
- it('should warn about low update interval for developer portal', () => {
188
- const config: PluginConfig = {
189
- platform: 'DaikinCloud',
190
- authMode: 'developer_portal',
191
- clientId: 'test-id',
192
- clientSecret: 'test-secret',
193
- callbackServerExternalAddress: '192.168.1.100',
194
- updateIntervalInMinutes: 5,
195
- };
196
- const manager = new ConfigManager(config);
197
- const result = manager.validate();
198
-
199
- expect(result.valid).toBe(true);
200
- expect(result.warnings.some(w => w.includes('rate limit'))).toBe(true);
201
- });
202
- });
203
-
204
- describe('getUpdateIntervalMs', () => {
205
- it('should return configured interval in milliseconds', () => {
206
- const config: PluginConfig = {
207
- platform: 'DaikinCloud',
208
- updateIntervalInMinutes: 30,
209
- };
210
- const manager = new ConfigManager(config);
211
- expect(manager.getUpdateIntervalMs()).toBe(30 * 60 * 1000);
212
- });
213
-
214
- it('should return default interval when not configured', () => {
215
- const config: PluginConfig = {
216
- platform: 'DaikinCloud',
217
- };
218
- const manager = new ConfigManager(config);
219
- expect(manager.getUpdateIntervalMs()).toBe(15 * 60 * 1000);
220
- });
221
- });
222
-
223
- describe('isDeviceExcluded', () => {
224
- it('should return true for excluded device', () => {
225
- const config: PluginConfig = {
226
- platform: 'DaikinCloud',
227
- excludedDevicesByDeviceId: ['device1', 'device2'],
228
- };
229
- const manager = new ConfigManager(config);
230
- expect(manager.isDeviceExcluded('device1')).toBe(true);
231
- });
232
-
233
- it('should return false for non-excluded device', () => {
234
- const config: PluginConfig = {
235
- platform: 'DaikinCloud',
236
- excludedDevicesByDeviceId: ['device1', 'device2'],
237
- };
238
- const manager = new ConfigManager(config);
239
- expect(manager.isDeviceExcluded('device3')).toBe(false);
240
- });
241
- });
242
-
243
- describe('getFeatures', () => {
244
- it('should return feature configuration', () => {
245
- const config: PluginConfig = {
246
- platform: 'DaikinCloud',
247
- showPowerfulMode: true,
248
- showEconoMode: false,
249
- };
250
- const manager = new ConfigManager(config);
251
- const features = manager.getFeatures();
252
-
253
- expect(features.powerfulMode).toBe(true);
254
- expect(features.econoMode).toBe(false);
255
- });
256
-
257
- it('should use legacy showExtraFeatures when present', () => {
258
- const config: PluginConfig = {
259
- platform: 'DaikinCloud',
260
- showExtraFeatures: true,
261
- };
262
- const manager = new ConfigManager(config);
263
- const features = manager.getFeatures();
264
-
265
- // All features should be true when legacy flag is set
266
- expect(features.powerfulMode).toBe(true);
267
- expect(features.econoMode).toBe(true);
268
- expect(features.streamerMode).toBe(true);
269
- });
270
- });
271
- });
@@ -1,73 +0,0 @@
1
- import {PlatformAccessory} from 'homebridge/lib/platformAccessory';
2
- import {DaikinCloudDevice, DaikinApi} from '../../../src/api';
3
- import {dx4Airco} from '../../fixtures/dx4-airco';
4
- import {althermaHeatPump} from '../../fixtures/altherma-heat-pump';
5
-
6
- const mockApi = { updateDevice: jest.fn().mockResolvedValue(undefined) } as unknown as DaikinApi;
7
-
8
- it('Get deviceModel from device', async () => {
9
- const accessory = new PlatformAccessory('NAME', 'efd08509-2edb-41d0-a9ab-ce913323d811');
10
- accessory.context.device = new DaikinCloudDevice(dx4Airco as any, mockApi);
11
- expect(accessory.context.device.getDescription().deviceModel).toEqual('dx4');
12
- });
13
-
14
- it('Get name from AC device', async () => {
15
- const accessory = new PlatformAccessory('NAME', 'efd08509-2edb-41d0-a9ab-ce913323d811');
16
- accessory.context.device = new DaikinCloudDevice(dx4Airco as any, mockApi);
17
- expect(accessory.context.device.getData('climateControl', 'name', undefined).value).toEqual('Zolder');
18
- });
19
-
20
- it('Get name from Altherma device', async () => {
21
- const accessory = new PlatformAccessory('NAME', 'efd08509-2edb-41d0-a9ab-ce913323d811');
22
- accessory.context.device = new DaikinCloudDevice(althermaHeatPump as any, mockApi);
23
- expect(accessory.context.device.getData('climateControlMainZone', 'name', undefined).value).toEqual('Altherma');
24
- });
25
-
26
- it('Get tankTemperature from device domesticHotWaterTank', async () => {
27
- const accessory = new PlatformAccessory('NAME', 'efd08509-2edb-41d0-a9ab-ce913323d811');
28
- accessory.context.device = new DaikinCloudDevice(althermaHeatPump as any, mockApi);
29
- expect(accessory.context.device.getData('domesticHotWaterTank', 'sensoryData', '/tankTemperature').value).toEqual(48);
30
- });
31
-
32
- it('Get roomTemperature from climateControl sensoryData', async () => {
33
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
34
- const result = device.getData('climateControl', 'sensoryData', '/roomTemperature');
35
- expect(result.value).toEqual(25);
36
- });
37
-
38
- it('getData navigates nested sensoryData path correctly', () => {
39
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
40
-
41
- const desc = device.desc;
42
- const mp = desc.managementPoints?.find((m: any) => m.embeddedId === 'climateControl');
43
- expect(mp).toBeDefined();
44
- expect(mp?.sensoryData).toBeDefined();
45
- expect(mp?.sensoryData?.value?.roomTemperature?.value).toEqual(25);
46
-
47
- const result = device.getData('climateControl', 'sensoryData', '/roomTemperature');
48
- expect(result.value).toEqual(25);
49
- });
50
-
51
- it('getData falls back to roomTemperature when controlMode is absent', () => {
52
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
53
-
54
- const managementPointId = 'climateControl';
55
- const controlModeResult = device.getData(managementPointId, 'controlMode', undefined);
56
- const controlMode = controlModeResult.value ? controlModeResult.value : 'roomTemperature';
57
-
58
- const path = '/' + controlMode;
59
- const temperature = device.getData(managementPointId, 'sensoryData', path);
60
- expect(temperature.value).toEqual(25);
61
- });
62
-
63
- it('updateRawData replaces device data', () => {
64
- const device = new DaikinCloudDevice(dx4Airco as any, mockApi);
65
- const originalId = device.getId();
66
-
67
- const modifiedData = { ...dx4Airco, id: 'new-id' } as any;
68
- device.updateRawData(modifiedData);
69
-
70
- expect(device.getId()).toEqual('new-id');
71
- expect(device.desc.id).toEqual('new-id');
72
- expect(device.getId()).not.toEqual(originalId);
73
- });