pumaguard 21.post29__py3-none-any.whl → 21.post83__py3-none-any.whl

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 (188) hide show
  1. pumaguard/presets.py +1 -0
  2. pumaguard/pumaguard-ui/.last_build_id +1 -1
  3. pumaguard/pumaguard-ui/assets/NOTICES +621 -71
  4. pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf +0 -0
  5. pumaguard/pumaguard-ui/flutter_bootstrap.js +1 -1
  6. pumaguard/pumaguard-ui/main.dart.js +28869 -28787
  7. pumaguard/web_routes/dhcp.py +311 -54
  8. pumaguard/web_routes/diagnostics.py +6 -0
  9. pumaguard/web_routes/settings.py +13 -0
  10. pumaguard/web_ui.py +29 -0
  11. {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/METADATA +1 -1
  12. pumaguard-21.post83.dist-info/RECORD +254 -0
  13. pumaguard-ui/.gitignore +48 -0
  14. pumaguard-ui/.metadata +45 -0
  15. pumaguard-ui/API_REFERENCE.md +717 -0
  16. pumaguard-ui/LICENSE +201 -0
  17. pumaguard-ui/Makefile +36 -0
  18. pumaguard-ui/README.md +371 -0
  19. pumaguard-ui/UI_DEVELOPMENT_CONTEXT.md +427 -0
  20. pumaguard-ui/analysis_options.yaml +28 -0
  21. pumaguard-ui/android/.gitignore +14 -0
  22. pumaguard-ui/android/app/build.gradle.kts +44 -0
  23. pumaguard-ui/android/app/src/debug/AndroidManifest.xml +7 -0
  24. pumaguard-ui/android/app/src/main/AndroidManifest.xml +45 -0
  25. pumaguard-ui/android/app/src/main/kotlin/com/example/pumaguard_ui/MainActivity.kt +5 -0
  26. pumaguard-ui/android/app/src/main/res/drawable/launch_background.xml +12 -0
  27. pumaguard-ui/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  28. pumaguard-ui/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  29. pumaguard-ui/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  30. pumaguard-ui/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  31. pumaguard-ui/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  32. pumaguard-ui/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. pumaguard-ui/android/app/src/main/res/values/styles.xml +18 -0
  34. pumaguard-ui/android/app/src/main/res/values-night/styles.xml +18 -0
  35. pumaguard-ui/android/app/src/profile/AndroidManifest.xml +7 -0
  36. pumaguard-ui/android/build.gradle.kts +24 -0
  37. pumaguard-ui/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  38. pumaguard-ui/android/gradle.properties +2 -0
  39. pumaguard-ui/android/settings.gradle.kts +26 -0
  40. pumaguard-ui/fonts/README.md +38 -0
  41. pumaguard-ui/fonts/Roboto-Bold.ttf +0 -0
  42. pumaguard-ui/fonts/Roboto-Light.ttf +0 -0
  43. pumaguard-ui/fonts/Roboto-Medium.ttf +0 -0
  44. pumaguard-ui/fonts/Roboto-Regular.ttf +0 -0
  45. pumaguard-ui/fonts/RobotoMono-Bold.ttf +0 -0
  46. pumaguard-ui/fonts/RobotoMono-Medium.ttf +0 -0
  47. pumaguard-ui/fonts/RobotoMono-Regular.ttf +0 -0
  48. pumaguard-ui/fonts/download_fonts.sh +76 -0
  49. pumaguard-ui/ios/.gitignore +34 -0
  50. pumaguard-ui/ios/Flutter/AppFrameworkInfo.plist +26 -0
  51. pumaguard-ui/ios/Flutter/Debug.xcconfig +1 -0
  52. pumaguard-ui/ios/Flutter/Release.xcconfig +1 -0
  53. pumaguard-ui/ios/Runner/AppDelegate.swift +13 -0
  54. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  55. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  56. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  57. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  58. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  59. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  60. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  61. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  62. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  63. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  64. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  65. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  66. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  67. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  68. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  69. pumaguard-ui/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  70. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  71. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  72. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  73. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  74. pumaguard-ui/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  75. pumaguard-ui/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
  76. pumaguard-ui/ios/Runner/Base.lproj/Main.storyboard +26 -0
  77. pumaguard-ui/ios/Runner/Info.plist +49 -0
  78. pumaguard-ui/ios/Runner/Runner-Bridging-Header.h +1 -0
  79. pumaguard-ui/ios/Runner.xcodeproj/project.pbxproj +616 -0
  80. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  81. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  82. pumaguard-ui/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  83. pumaguard-ui/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  84. pumaguard-ui/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  85. pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  86. pumaguard-ui/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  87. pumaguard-ui/ios/RunnerTests/RunnerTests.swift +12 -0
  88. pumaguard-ui/lib/main.dart +56 -0
  89. pumaguard-ui/lib/models/camera.dart +45 -0
  90. pumaguard-ui/lib/models/plug.dart +45 -0
  91. pumaguard-ui/lib/models/settings.dart +112 -0
  92. pumaguard-ui/lib/models/status.dart +58 -0
  93. pumaguard-ui/lib/screens/directories_screen.dart +319 -0
  94. pumaguard-ui/lib/screens/home_screen.dart +545 -0
  95. pumaguard-ui/lib/screens/image_browser_screen.dart +1248 -0
  96. pumaguard-ui/lib/screens/server_discovery_screen.dart +390 -0
  97. pumaguard-ui/lib/screens/settings_screen.dart +1162 -0
  98. pumaguard-ui/lib/screens/wifi_settings_screen.dart +671 -0
  99. pumaguard-ui/lib/services/api_service.dart +717 -0
  100. pumaguard-ui/lib/services/camera_events_service.dart +195 -0
  101. pumaguard-ui/lib/services/mdns_service.dart +4 -0
  102. pumaguard-ui/lib/services/mdns_service_impl.dart +282 -0
  103. pumaguard-ui/lib/services/mdns_service_io.dart +1 -0
  104. pumaguard-ui/lib/services/mdns_service_web.dart +106 -0
  105. pumaguard-ui/lib/utils/download_helper.dart +2 -0
  106. pumaguard-ui/lib/utils/download_helper_stub.dart +6 -0
  107. pumaguard-ui/lib/utils/download_helper_web.dart +14 -0
  108. pumaguard-ui/lib/utils/platform_url.dart +10 -0
  109. pumaguard-ui/lib/utils/platform_url_stub.dart +11 -0
  110. pumaguard-ui/lib/utils/platform_url_web.dart +16 -0
  111. pumaguard-ui/linux/.gitignore +1 -0
  112. pumaguard-ui/linux/CMakeLists.txt +128 -0
  113. pumaguard-ui/linux/flutter/CMakeLists.txt +88 -0
  114. pumaguard-ui/linux/flutter/generated_plugin_registrant.cc +15 -0
  115. pumaguard-ui/linux/flutter/generated_plugin_registrant.h +15 -0
  116. pumaguard-ui/linux/flutter/generated_plugins.cmake +24 -0
  117. pumaguard-ui/linux/runner/CMakeLists.txt +26 -0
  118. pumaguard-ui/linux/runner/main.cc +6 -0
  119. pumaguard-ui/linux/runner/my_application.cc +148 -0
  120. pumaguard-ui/linux/runner/my_application.h +21 -0
  121. pumaguard-ui/macos/.gitignore +7 -0
  122. pumaguard-ui/macos/Flutter/Flutter-Debug.xcconfig +1 -0
  123. pumaguard-ui/macos/Flutter/Flutter-Release.xcconfig +1 -0
  124. pumaguard-ui/macos/Flutter/GeneratedPluginRegistrant.swift +16 -0
  125. pumaguard-ui/macos/Runner/AppDelegate.swift +13 -0
  126. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  127. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  128. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  129. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  130. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  131. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  132. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  133. pumaguard-ui/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  134. pumaguard-ui/macos/Runner/Base.lproj/MainMenu.xib +343 -0
  135. pumaguard-ui/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  136. pumaguard-ui/macos/Runner/Configs/Debug.xcconfig +2 -0
  137. pumaguard-ui/macos/Runner/Configs/Release.xcconfig +2 -0
  138. pumaguard-ui/macos/Runner/Configs/Warnings.xcconfig +13 -0
  139. pumaguard-ui/macos/Runner/DebugProfile.entitlements +12 -0
  140. pumaguard-ui/macos/Runner/Info.plist +32 -0
  141. pumaguard-ui/macos/Runner/MainFlutterWindow.swift +15 -0
  142. pumaguard-ui/macos/Runner/Release.entitlements +8 -0
  143. pumaguard-ui/macos/Runner.xcodeproj/project.pbxproj +705 -0
  144. pumaguard-ui/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  145. pumaguard-ui/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  146. pumaguard-ui/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  147. pumaguard-ui/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  148. pumaguard-ui/macos/RunnerTests/RunnerTests.swift +12 -0
  149. pumaguard-ui/pubspec.lock +882 -0
  150. pumaguard-ui/pubspec.yaml +125 -0
  151. pumaguard-ui/test/models/camera_test.dart +515 -0
  152. pumaguard-ui/test/models/plug_test.dart +499 -0
  153. pumaguard-ui/test/models/settings_test.dart +903 -0
  154. pumaguard-ui/test/models/status_test.dart +707 -0
  155. pumaguard-ui/test/screens/image_browser_grouping_test.dart +555 -0
  156. pumaguard-ui/test/services/api_service_cameras_test.dart +580 -0
  157. pumaguard-ui/test/services/api_service_image_browser_test.dart +512 -0
  158. pumaguard-ui/test/widget_test.dart.skip +38 -0
  159. pumaguard-ui/web/favicon.png +0 -0
  160. pumaguard-ui/web/icons/Icon-192.png +0 -0
  161. pumaguard-ui/web/icons/Icon-512.png +0 -0
  162. pumaguard-ui/web/icons/Icon-maskable-192.png +0 -0
  163. pumaguard-ui/web/icons/Icon-maskable-512.png +0 -0
  164. pumaguard-ui/web/index.html +38 -0
  165. pumaguard-ui/web/manifest.json +35 -0
  166. pumaguard-ui/windows/.gitignore +17 -0
  167. pumaguard-ui/windows/CMakeLists.txt +108 -0
  168. pumaguard-ui/windows/flutter/CMakeLists.txt +109 -0
  169. pumaguard-ui/windows/flutter/generated_plugin_registrant.cc +14 -0
  170. pumaguard-ui/windows/flutter/generated_plugin_registrant.h +15 -0
  171. pumaguard-ui/windows/flutter/generated_plugins.cmake +24 -0
  172. pumaguard-ui/windows/runner/CMakeLists.txt +40 -0
  173. pumaguard-ui/windows/runner/Runner.rc +121 -0
  174. pumaguard-ui/windows/runner/flutter_window.cpp +71 -0
  175. pumaguard-ui/windows/runner/flutter_window.h +33 -0
  176. pumaguard-ui/windows/runner/main.cpp +43 -0
  177. pumaguard-ui/windows/runner/resource.h +16 -0
  178. pumaguard-ui/windows/runner/resources/app_icon.ico +0 -0
  179. pumaguard-ui/windows/runner/runner.exe.manifest +14 -0
  180. pumaguard-ui/windows/runner/utils.cpp +65 -0
  181. pumaguard-ui/windows/runner/utils.h +19 -0
  182. pumaguard-ui/windows/runner/win32_window.cpp +288 -0
  183. pumaguard-ui/windows/runner/win32_window.h +102 -0
  184. pumaguard-21.post29.dist-info/RECORD +0 -83
  185. {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/WHEEL +0 -0
  186. {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/entry_points.txt +0 -0
  187. {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/licenses/LICENSE +0 -0
  188. {pumaguard-21.post29.dist-info → pumaguard-21.post83.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,499 @@
1
+ // Unit tests for Plug model
2
+ // Tests JSON serialization, deserialization, and helper methods
3
+
4
+ import 'package:flutter_test/flutter_test.dart';
5
+ import 'package:pumaguard_ui/models/plug.dart';
6
+
7
+ void main() {
8
+ group('Plug Model Tests', () {
9
+ group('Constructor', () {
10
+ test('creates Plug with all fields', () {
11
+ final plug = Plug(
12
+ hostname: 'TestPlug',
13
+ ipAddress: '192.168.52.200',
14
+ macAddress: 'aa:bb:cc:dd:ee:ff',
15
+ lastSeen: '2024-01-15T10:30:00Z',
16
+ status: 'connected',
17
+ );
18
+
19
+ expect(plug.hostname, 'TestPlug');
20
+ expect(plug.ipAddress, '192.168.52.200');
21
+ expect(plug.macAddress, 'aa:bb:cc:dd:ee:ff');
22
+ expect(plug.lastSeen, '2024-01-15T10:30:00Z');
23
+ expect(plug.status, 'connected');
24
+ });
25
+
26
+ test('creates Plug with empty strings', () {
27
+ final plug = Plug(
28
+ hostname: '',
29
+ ipAddress: '',
30
+ macAddress: '',
31
+ lastSeen: '',
32
+ status: '',
33
+ );
34
+
35
+ expect(plug.hostname, '');
36
+ expect(plug.ipAddress, '');
37
+ expect(plug.macAddress, '');
38
+ expect(plug.lastSeen, '');
39
+ expect(plug.status, '');
40
+ });
41
+ });
42
+
43
+ group('fromJson', () {
44
+ test('parses complete JSON correctly', () {
45
+ final json = {
46
+ 'hostname': 'KasaPlug1',
47
+ 'ip_address': '192.168.52.201',
48
+ 'mac_address': 'bb:cc:dd:ee:ff:01',
49
+ 'last_seen': '2024-01-15T10:30:00Z',
50
+ 'status': 'connected',
51
+ };
52
+
53
+ final plug = Plug.fromJson(json);
54
+
55
+ expect(plug.hostname, 'KasaPlug1');
56
+ expect(plug.ipAddress, '192.168.52.201');
57
+ expect(plug.macAddress, 'bb:cc:dd:ee:ff:01');
58
+ expect(plug.lastSeen, '2024-01-15T10:30:00Z');
59
+ expect(plug.status, 'connected');
60
+ });
61
+
62
+ test('handles null values with defaults', () {
63
+ final json = {
64
+ 'hostname': null,
65
+ 'ip_address': null,
66
+ 'mac_address': null,
67
+ 'last_seen': null,
68
+ 'status': null,
69
+ };
70
+
71
+ final plug = Plug.fromJson(json);
72
+
73
+ expect(plug.hostname, '');
74
+ expect(plug.ipAddress, '');
75
+ expect(plug.macAddress, '');
76
+ expect(plug.lastSeen, '');
77
+ expect(plug.status, 'unknown');
78
+ });
79
+
80
+ test('handles missing fields with defaults', () {
81
+ final json = <String, dynamic>{};
82
+
83
+ final plug = Plug.fromJson(json);
84
+
85
+ expect(plug.hostname, '');
86
+ expect(plug.ipAddress, '');
87
+ expect(plug.macAddress, '');
88
+ expect(plug.lastSeen, '');
89
+ expect(plug.status, 'unknown');
90
+ });
91
+
92
+ test('handles partial JSON with some fields missing', () {
93
+ final json = {
94
+ 'hostname': 'PartialPlug',
95
+ 'ip_address': '192.168.52.200',
96
+ };
97
+
98
+ final plug = Plug.fromJson(json);
99
+
100
+ expect(plug.hostname, 'PartialPlug');
101
+ expect(plug.ipAddress, '192.168.52.200');
102
+ expect(plug.macAddress, '');
103
+ expect(plug.lastSeen, '');
104
+ expect(plug.status, 'unknown');
105
+ });
106
+ });
107
+
108
+ group('toJson', () {
109
+ test('converts Plug to JSON correctly', () {
110
+ final plug = Plug(
111
+ hostname: 'TestPlug',
112
+ ipAddress: '192.168.52.200',
113
+ macAddress: 'aa:bb:cc:dd:ee:ff',
114
+ lastSeen: '2024-01-15T10:30:00Z',
115
+ status: 'connected',
116
+ );
117
+
118
+ final json = plug.toJson();
119
+
120
+ expect(json['hostname'], 'TestPlug');
121
+ expect(json['ip_address'], '192.168.52.200');
122
+ expect(json['mac_address'], 'aa:bb:cc:dd:ee:ff');
123
+ expect(json['last_seen'], '2024-01-15T10:30:00Z');
124
+ expect(json['status'], 'connected');
125
+ });
126
+
127
+ test('preserves empty strings in JSON', () {
128
+ final plug = Plug(
129
+ hostname: '',
130
+ ipAddress: '',
131
+ macAddress: '',
132
+ lastSeen: '',
133
+ status: '',
134
+ );
135
+
136
+ final json = plug.toJson();
137
+
138
+ expect(json['hostname'], '');
139
+ expect(json['ip_address'], '');
140
+ expect(json['mac_address'], '');
141
+ expect(json['last_seen'], '');
142
+ expect(json['status'], '');
143
+ });
144
+ });
145
+
146
+ group('JSON Round Trip', () {
147
+ test('fromJson and toJson are inverses', () {
148
+ final originalJson = {
149
+ 'hostname': 'RoundTripPlug',
150
+ 'ip_address': '192.168.52.250',
151
+ 'mac_address': 'aa:bb:cc:dd:ee:99',
152
+ 'last_seen': '2024-01-15T12:00:00Z',
153
+ 'status': 'disconnected',
154
+ };
155
+
156
+ final plug = Plug.fromJson(originalJson);
157
+ final resultJson = plug.toJson();
158
+
159
+ expect(resultJson['hostname'], originalJson['hostname']);
160
+ expect(resultJson['ip_address'], originalJson['ip_address']);
161
+ expect(resultJson['mac_address'], originalJson['mac_address']);
162
+ expect(resultJson['last_seen'], originalJson['last_seen']);
163
+ expect(resultJson['status'], originalJson['status']);
164
+ });
165
+ });
166
+
167
+ group('isConnected', () {
168
+ test('returns true when status is "connected"', () {
169
+ final plug = Plug(
170
+ hostname: 'TestPlug',
171
+ ipAddress: '192.168.52.200',
172
+ macAddress: 'aa:bb:cc:dd:ee:ff',
173
+ lastSeen: '2024-01-15T10:30:00Z',
174
+ status: 'connected',
175
+ );
176
+
177
+ expect(plug.isConnected, true);
178
+ });
179
+
180
+ test('returns false when status is "disconnected"', () {
181
+ final plug = Plug(
182
+ hostname: 'TestPlug',
183
+ ipAddress: '192.168.52.200',
184
+ macAddress: 'aa:bb:cc:dd:ee:ff',
185
+ lastSeen: '2024-01-15T10:30:00Z',
186
+ status: 'disconnected',
187
+ );
188
+
189
+ expect(plug.isConnected, false);
190
+ });
191
+
192
+ test('returns false when status is "unknown"', () {
193
+ final plug = Plug(
194
+ hostname: 'TestPlug',
195
+ ipAddress: '192.168.52.200',
196
+ macAddress: 'aa:bb:cc:dd:ee:ff',
197
+ lastSeen: '2024-01-15T10:30:00Z',
198
+ status: 'unknown',
199
+ );
200
+
201
+ expect(plug.isConnected, false);
202
+ });
203
+
204
+ test('returns false when status is empty', () {
205
+ final plug = Plug(
206
+ hostname: 'TestPlug',
207
+ ipAddress: '192.168.52.200',
208
+ macAddress: 'aa:bb:cc:dd:ee:ff',
209
+ lastSeen: '2024-01-15T10:30:00Z',
210
+ status: '',
211
+ );
212
+
213
+ expect(plug.isConnected, false);
214
+ });
215
+
216
+ test('returns false when status is any other value', () {
217
+ final plug = Plug(
218
+ hostname: 'TestPlug',
219
+ ipAddress: '192.168.52.200',
220
+ macAddress: 'aa:bb:cc:dd:ee:ff',
221
+ lastSeen: '2024-01-15T10:30:00Z',
222
+ status: 'pending',
223
+ );
224
+
225
+ expect(plug.isConnected, false);
226
+ });
227
+
228
+ test('is case-sensitive', () {
229
+ final plug = Plug(
230
+ hostname: 'TestPlug',
231
+ ipAddress: '192.168.52.200',
232
+ macAddress: 'aa:bb:cc:dd:ee:ff',
233
+ lastSeen: '2024-01-15T10:30:00Z',
234
+ status: 'Connected', // Capital C
235
+ );
236
+
237
+ expect(plug.isConnected, false);
238
+ });
239
+ });
240
+
241
+ group('displayName', () {
242
+ test('returns hostname when hostname is not empty', () {
243
+ final plug = Plug(
244
+ hostname: 'MySmartPlug',
245
+ ipAddress: '192.168.52.200',
246
+ macAddress: 'aa:bb:cc:dd:ee:ff',
247
+ lastSeen: '2024-01-15T10:30:00Z',
248
+ status: 'connected',
249
+ );
250
+
251
+ expect(plug.displayName, 'MySmartPlug');
252
+ });
253
+
254
+ test('returns IP address when hostname is empty', () {
255
+ final plug = Plug(
256
+ hostname: '',
257
+ ipAddress: '192.168.52.200',
258
+ macAddress: 'aa:bb:cc:dd:ee:ff',
259
+ lastSeen: '2024-01-15T10:30:00Z',
260
+ status: 'connected',
261
+ );
262
+
263
+ expect(plug.displayName, '192.168.52.200');
264
+ });
265
+
266
+ test('returns empty string when both hostname and IP are empty', () {
267
+ final plug = Plug(
268
+ hostname: '',
269
+ ipAddress: '',
270
+ macAddress: 'aa:bb:cc:dd:ee:ff',
271
+ lastSeen: '2024-01-15T10:30:00Z',
272
+ status: 'connected',
273
+ );
274
+
275
+ expect(plug.displayName, '');
276
+ });
277
+
278
+ test('prefers hostname over IP address', () {
279
+ final plug = Plug(
280
+ hostname: 'PreferredName',
281
+ ipAddress: '192.168.52.200',
282
+ macAddress: 'aa:bb:cc:dd:ee:ff',
283
+ lastSeen: '2024-01-15T10:30:00Z',
284
+ status: 'connected',
285
+ );
286
+
287
+ expect(plug.displayName, 'PreferredName');
288
+ expect(plug.displayName, isNot('192.168.52.200'));
289
+ });
290
+ });
291
+
292
+ group('plugUrl', () {
293
+ test('returns IP address', () {
294
+ final plug = Plug(
295
+ hostname: 'TestPlug',
296
+ ipAddress: '192.168.52.200',
297
+ macAddress: 'aa:bb:cc:dd:ee:ff',
298
+ lastSeen: '2024-01-15T10:30:00Z',
299
+ status: 'connected',
300
+ );
301
+
302
+ expect(plug.plugUrl, '192.168.52.200');
303
+ });
304
+
305
+ test('returns empty string when IP is empty', () {
306
+ final plug = Plug(
307
+ hostname: 'TestPlug',
308
+ ipAddress: '',
309
+ macAddress: 'aa:bb:cc:dd:ee:ff',
310
+ lastSeen: '2024-01-15T10:30:00Z',
311
+ status: 'connected',
312
+ );
313
+
314
+ expect(plug.plugUrl, '');
315
+ });
316
+
317
+ test('handles IPv4 addresses', () {
318
+ final plug = Plug(
319
+ hostname: 'TestPlug',
320
+ ipAddress: '10.0.0.1',
321
+ macAddress: 'aa:bb:cc:dd:ee:ff',
322
+ lastSeen: '2024-01-15T10:30:00Z',
323
+ status: 'connected',
324
+ );
325
+
326
+ expect(plug.plugUrl, '10.0.0.1');
327
+ });
328
+
329
+ test('handles IPv6 addresses', () {
330
+ final plug = Plug(
331
+ hostname: 'TestPlug',
332
+ ipAddress: 'fe80::1',
333
+ macAddress: 'aa:bb:cc:dd:ee:ff',
334
+ lastSeen: '2024-01-15T10:30:00Z',
335
+ status: 'connected',
336
+ );
337
+
338
+ expect(plug.plugUrl, 'fe80::1');
339
+ });
340
+
341
+ test('handles IP with port', () {
342
+ final plug = Plug(
343
+ hostname: 'TestPlug',
344
+ ipAddress: '192.168.52.200:9999',
345
+ macAddress: 'aa:bb:cc:dd:ee:ff',
346
+ lastSeen: '2024-01-15T10:30:00Z',
347
+ status: 'connected',
348
+ );
349
+
350
+ expect(plug.plugUrl, '192.168.52.200:9999');
351
+ });
352
+
353
+ test('handles localhost', () {
354
+ final plug = Plug(
355
+ hostname: 'TestPlug',
356
+ ipAddress: 'localhost',
357
+ macAddress: 'aa:bb:cc:dd:ee:ff',
358
+ lastSeen: '2024-01-15T10:30:00Z',
359
+ status: 'connected',
360
+ );
361
+
362
+ expect(plug.plugUrl, 'localhost');
363
+ });
364
+ });
365
+
366
+ group('Special Characters and Edge Cases', () {
367
+ test('handles hostname with spaces', () {
368
+ final plug = Plug(
369
+ hostname: 'My Smart Plug',
370
+ ipAddress: '192.168.52.200',
371
+ macAddress: 'aa:bb:cc:dd:ee:ff',
372
+ lastSeen: '2024-01-15T10:30:00Z',
373
+ status: 'connected',
374
+ );
375
+
376
+ expect(plug.hostname, 'My Smart Plug');
377
+ expect(plug.displayName, 'My Smart Plug');
378
+ });
379
+
380
+ test('handles hostname with special characters', () {
381
+ final plug = Plug(
382
+ hostname: 'Plug-1_Test@Site#A',
383
+ ipAddress: '192.168.52.200',
384
+ macAddress: 'aa:bb:cc:dd:ee:ff',
385
+ lastSeen: '2024-01-15T10:30:00Z',
386
+ status: 'connected',
387
+ );
388
+
389
+ expect(plug.hostname, 'Plug-1_Test@Site#A');
390
+ });
391
+
392
+ test('handles very long hostname', () {
393
+ final longHostname = 'P' * 100;
394
+ final plug = Plug(
395
+ hostname: longHostname,
396
+ ipAddress: '192.168.52.200',
397
+ macAddress: 'aa:bb:cc:dd:ee:ff',
398
+ lastSeen: '2024-01-15T10:30:00Z',
399
+ status: 'connected',
400
+ );
401
+
402
+ expect(plug.hostname, longHostname);
403
+ expect(plug.displayName.length, 100);
404
+ });
405
+
406
+ test('handles MAC address with uppercase letters', () {
407
+ final plug = Plug(
408
+ hostname: 'TestPlug',
409
+ ipAddress: '192.168.52.200',
410
+ macAddress: 'AA:BB:CC:DD:EE:FF',
411
+ lastSeen: '2024-01-15T10:30:00Z',
412
+ status: 'connected',
413
+ );
414
+
415
+ expect(plug.macAddress, 'AA:BB:CC:DD:EE:FF');
416
+ });
417
+
418
+ test('handles MAC address without colons', () {
419
+ final plug = Plug(
420
+ hostname: 'TestPlug',
421
+ ipAddress: '192.168.52.200',
422
+ macAddress: 'aabbccddeeff',
423
+ lastSeen: '2024-01-15T10:30:00Z',
424
+ status: 'connected',
425
+ );
426
+
427
+ expect(plug.macAddress, 'aabbccddeeff');
428
+ });
429
+
430
+ test('handles ISO8601 timestamp formats', () {
431
+ final timestamps = [
432
+ '2024-01-15T10:30:00Z',
433
+ '2024-01-15T10:30:00.123Z',
434
+ '2024-01-15T10:30:00+00:00',
435
+ '2024-01-15T10:30:00-05:00',
436
+ ];
437
+
438
+ for (final timestamp in timestamps) {
439
+ final plug = Plug(
440
+ hostname: 'TestPlug',
441
+ ipAddress: '192.168.52.200',
442
+ macAddress: 'aa:bb:cc:dd:ee:ff',
443
+ lastSeen: timestamp,
444
+ status: 'connected',
445
+ );
446
+
447
+ expect(plug.lastSeen, timestamp);
448
+ }
449
+ });
450
+ });
451
+
452
+ group('Real-World Scenarios', () {
453
+ test('creates plug from typical API response', () {
454
+ final json = {
455
+ 'hostname': 'KasaPlug',
456
+ 'ip_address': '192.168.52.201',
457
+ 'mac_address': 'bb:cc:dd:ee:ff:01',
458
+ 'last_seen': '2024-01-15T10:30:00Z',
459
+ 'status': 'connected',
460
+ };
461
+
462
+ final plug = Plug.fromJson(json);
463
+
464
+ expect(plug.hostname, 'KasaPlug');
465
+ expect(plug.isConnected, true);
466
+ expect(plug.displayName, 'KasaPlug');
467
+ expect(plug.plugUrl, '192.168.52.201');
468
+ });
469
+
470
+ test('creates disconnected plug', () {
471
+ final json = {
472
+ 'hostname': 'OldPlug',
473
+ 'ip_address': '192.168.52.250',
474
+ 'mac_address': 'ff:ff:ff:ff:ff:ff',
475
+ 'last_seen': '2024-01-14T08:00:00Z',
476
+ 'status': 'disconnected',
477
+ };
478
+
479
+ final plug = Plug.fromJson(json);
480
+
481
+ expect(plug.isConnected, false);
482
+ expect(plug.status, 'disconnected');
483
+ });
484
+
485
+ test('creates plug with minimal information', () {
486
+ final json = {
487
+ 'ip_address': '192.168.52.250',
488
+ 'mac_address': 'aa:bb:cc:dd:ee:99',
489
+ };
490
+
491
+ final plug = Plug.fromJson(json);
492
+
493
+ expect(plug.displayName, '192.168.52.250');
494
+ expect(plug.status, 'unknown');
495
+ expect(plug.isConnected, false);
496
+ });
497
+ });
498
+ });
499
+ }