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,707 @@
1
+ // Unit tests for Status model
2
+ // Tests JSON serialization, deserialization, and helper methods
3
+
4
+ import 'package:flutter_test/flutter_test.dart';
5
+ import 'package:pumaguard_ui/models/status.dart';
6
+
7
+ void main() {
8
+ group('Status Model Tests', () {
9
+ group('Constructor', () {
10
+ test('creates Status with all fields', () {
11
+ final status = Status(
12
+ status: 'running',
13
+ version: '1.2.3',
14
+ directoriesCount: 5,
15
+ host: 'localhost',
16
+ port: 5000,
17
+ uptimeSeconds: 3665,
18
+ );
19
+
20
+ expect(status.status, 'running');
21
+ expect(status.version, '1.2.3');
22
+ expect(status.directoriesCount, 5);
23
+ expect(status.host, 'localhost');
24
+ expect(status.port, 5000);
25
+ expect(status.uptimeSeconds, 3665);
26
+ });
27
+
28
+ test('creates Status with empty strings', () {
29
+ final status = Status(
30
+ status: '',
31
+ version: '',
32
+ directoriesCount: 0,
33
+ host: '',
34
+ port: 0,
35
+ uptimeSeconds: 0,
36
+ );
37
+
38
+ expect(status.status, '');
39
+ expect(status.version, '');
40
+ expect(status.directoriesCount, 0);
41
+ expect(status.host, '');
42
+ expect(status.port, 0);
43
+ expect(status.uptimeSeconds, 0);
44
+ });
45
+ });
46
+
47
+ group('fromJson', () {
48
+ test('parses complete JSON correctly', () {
49
+ final json = {
50
+ 'status': 'running',
51
+ 'version': '1.2.3',
52
+ 'directories_count': 5,
53
+ 'host': 'localhost',
54
+ 'port': 5000,
55
+ 'uptime_seconds': 3665,
56
+ };
57
+
58
+ final status = Status.fromJson(json);
59
+
60
+ expect(status.status, 'running');
61
+ expect(status.version, '1.2.3');
62
+ expect(status.directoriesCount, 5);
63
+ expect(status.host, 'localhost');
64
+ expect(status.port, 5000);
65
+ expect(status.uptimeSeconds, 3665);
66
+ });
67
+
68
+ test('handles null values with defaults', () {
69
+ final json = {
70
+ 'status': null,
71
+ 'version': null,
72
+ 'directories_count': null,
73
+ 'host': null,
74
+ 'port': null,
75
+ 'uptime_seconds': null,
76
+ };
77
+
78
+ final status = Status.fromJson(json);
79
+
80
+ expect(status.status, 'unknown');
81
+ expect(status.version, '0.0.0');
82
+ expect(status.directoriesCount, 0);
83
+ expect(status.host, 'localhost');
84
+ expect(status.port, 5000);
85
+ expect(status.uptimeSeconds, 0);
86
+ });
87
+
88
+ test('handles missing fields with defaults', () {
89
+ final json = <String, dynamic>{};
90
+
91
+ final status = Status.fromJson(json);
92
+
93
+ expect(status.status, 'unknown');
94
+ expect(status.version, '0.0.0');
95
+ expect(status.directoriesCount, 0);
96
+ expect(status.host, 'localhost');
97
+ expect(status.port, 5000);
98
+ expect(status.uptimeSeconds, 0);
99
+ });
100
+
101
+ test('handles partial JSON with some fields missing', () {
102
+ final json = {'status': 'stopped', 'version': '2.0.0'};
103
+
104
+ final status = Status.fromJson(json);
105
+
106
+ expect(status.status, 'stopped');
107
+ expect(status.version, '2.0.0');
108
+ expect(status.directoriesCount, 0);
109
+ expect(status.host, 'localhost');
110
+ expect(status.port, 5000);
111
+ expect(status.uptimeSeconds, 0);
112
+ });
113
+
114
+ test('handles different status values', () {
115
+ final statuses = ['running', 'stopped', 'error', 'idle', 'unknown'];
116
+
117
+ for (final statusValue in statuses) {
118
+ final json = {'status': statusValue};
119
+ final status = Status.fromJson(json);
120
+ expect(status.status, statusValue);
121
+ }
122
+ });
123
+
124
+ test('handles different port numbers', () {
125
+ final ports = [5000, 8080, 3000, 80, 443, 65535];
126
+
127
+ for (final portValue in ports) {
128
+ final json = {'port': portValue};
129
+ final status = Status.fromJson(json);
130
+ expect(status.port, portValue);
131
+ }
132
+ });
133
+
134
+ test('handles different host values', () {
135
+ final hosts = [
136
+ 'localhost',
137
+ '127.0.0.1',
138
+ '192.168.1.100',
139
+ 'pumaguard.local',
140
+ '0.0.0.0',
141
+ ];
142
+
143
+ for (final hostValue in hosts) {
144
+ final json = {'host': hostValue};
145
+ final status = Status.fromJson(json);
146
+ expect(status.host, hostValue);
147
+ }
148
+ });
149
+ });
150
+
151
+ group('toJson', () {
152
+ test('converts Status to JSON correctly', () {
153
+ final status = Status(
154
+ status: 'running',
155
+ version: '1.2.3',
156
+ directoriesCount: 5,
157
+ host: 'localhost',
158
+ port: 5000,
159
+ uptimeSeconds: 3665,
160
+ );
161
+
162
+ final json = status.toJson();
163
+
164
+ expect(json['status'], 'running');
165
+ expect(json['version'], '1.2.3');
166
+ expect(json['directories_count'], 5);
167
+ expect(json['host'], 'localhost');
168
+ expect(json['port'], 5000);
169
+ expect(json['uptime_seconds'], 3665);
170
+ });
171
+
172
+ test('preserves empty strings in JSON', () {
173
+ final status = Status(
174
+ status: '',
175
+ version: '',
176
+ directoriesCount: 0,
177
+ host: '',
178
+ port: 0,
179
+ uptimeSeconds: 0,
180
+ );
181
+
182
+ final json = status.toJson();
183
+
184
+ expect(json['status'], '');
185
+ expect(json['version'], '');
186
+ expect(json['directories_count'], 0);
187
+ expect(json['host'], '');
188
+ expect(json['port'], 0);
189
+ expect(json['uptime_seconds'], 0);
190
+ });
191
+
192
+ test('preserves zero values in JSON', () {
193
+ final status = Status(
194
+ status: 'running',
195
+ version: '1.0.0',
196
+ directoriesCount: 0,
197
+ host: 'localhost',
198
+ port: 0,
199
+ uptimeSeconds: 0,
200
+ );
201
+
202
+ final json = status.toJson();
203
+
204
+ expect(json['directories_count'], 0);
205
+ expect(json['port'], 0);
206
+ expect(json['uptime_seconds'], 0);
207
+ });
208
+ });
209
+
210
+ group('JSON Round Trip', () {
211
+ test('fromJson and toJson are inverses', () {
212
+ final originalJson = {
213
+ 'status': 'running',
214
+ 'version': '1.2.3',
215
+ 'directories_count': 5,
216
+ 'host': 'localhost',
217
+ 'port': 5000,
218
+ 'uptime_seconds': 3665,
219
+ };
220
+
221
+ final status = Status.fromJson(originalJson);
222
+ final resultJson = status.toJson();
223
+
224
+ expect(resultJson['status'], originalJson['status']);
225
+ expect(resultJson['version'], originalJson['version']);
226
+ expect(
227
+ resultJson['directories_count'],
228
+ originalJson['directories_count'],
229
+ );
230
+ expect(resultJson['host'], originalJson['host']);
231
+ expect(resultJson['port'], originalJson['port']);
232
+ expect(resultJson['uptime_seconds'], originalJson['uptime_seconds']);
233
+ });
234
+ });
235
+
236
+ group('isRunning', () {
237
+ test('returns true when status is "running"', () {
238
+ final status = Status(
239
+ status: 'running',
240
+ version: '1.0.0',
241
+ directoriesCount: 1,
242
+ host: 'localhost',
243
+ port: 5000,
244
+ uptimeSeconds: 100,
245
+ );
246
+
247
+ expect(status.isRunning, true);
248
+ });
249
+
250
+ test('returns false when status is "stopped"', () {
251
+ final status = Status(
252
+ status: 'stopped',
253
+ version: '1.0.0',
254
+ directoriesCount: 1,
255
+ host: 'localhost',
256
+ port: 5000,
257
+ uptimeSeconds: 0,
258
+ );
259
+
260
+ expect(status.isRunning, false);
261
+ });
262
+
263
+ test('returns false when status is "error"', () {
264
+ final status = Status(
265
+ status: 'error',
266
+ version: '1.0.0',
267
+ directoriesCount: 1,
268
+ host: 'localhost',
269
+ port: 5000,
270
+ uptimeSeconds: 0,
271
+ );
272
+
273
+ expect(status.isRunning, false);
274
+ });
275
+
276
+ test('returns false when status is "unknown"', () {
277
+ final status = Status(
278
+ status: 'unknown',
279
+ version: '1.0.0',
280
+ directoriesCount: 1,
281
+ host: 'localhost',
282
+ port: 5000,
283
+ uptimeSeconds: 0,
284
+ );
285
+
286
+ expect(status.isRunning, false);
287
+ });
288
+
289
+ test('returns false when status is empty', () {
290
+ final status = Status(
291
+ status: '',
292
+ version: '1.0.0',
293
+ directoriesCount: 1,
294
+ host: 'localhost',
295
+ port: 5000,
296
+ uptimeSeconds: 0,
297
+ );
298
+
299
+ expect(status.isRunning, false);
300
+ });
301
+
302
+ test('is case-sensitive', () {
303
+ final status = Status(
304
+ status: 'Running', // Capital R
305
+ version: '1.0.0',
306
+ directoriesCount: 1,
307
+ host: 'localhost',
308
+ port: 5000,
309
+ uptimeSeconds: 100,
310
+ );
311
+
312
+ expect(status.isRunning, false);
313
+ });
314
+ });
315
+
316
+ group('uptimeFormatted', () {
317
+ test('formats seconds only', () {
318
+ final status = Status(
319
+ status: 'running',
320
+ version: '1.0.0',
321
+ directoriesCount: 1,
322
+ host: 'localhost',
323
+ port: 5000,
324
+ uptimeSeconds: 5,
325
+ );
326
+ expect(status.uptimeFormatted, '5s');
327
+ });
328
+
329
+ test('formats 59 seconds', () {
330
+ final status = Status(
331
+ status: 'running',
332
+ version: '1.0.0',
333
+ directoriesCount: 1,
334
+ host: 'localhost',
335
+ port: 5000,
336
+ uptimeSeconds: 59,
337
+ );
338
+ expect(status.uptimeFormatted, '59s');
339
+ });
340
+
341
+ test('formats exactly 1 minute', () {
342
+ final status = Status(
343
+ status: 'running',
344
+ version: '1.0.0',
345
+ directoriesCount: 1,
346
+ host: 'localhost',
347
+ port: 5000,
348
+ uptimeSeconds: 60,
349
+ );
350
+ expect(status.uptimeFormatted, '1m 0s');
351
+ });
352
+
353
+ test('formats minutes and seconds', () {
354
+ final status = Status(
355
+ status: 'running',
356
+ version: '1.0.0',
357
+ directoriesCount: 1,
358
+ host: 'localhost',
359
+ port: 5000,
360
+ uptimeSeconds: 65,
361
+ );
362
+ expect(status.uptimeFormatted, '1m 5s');
363
+ });
364
+
365
+ test('formats 59 minutes 59 seconds', () {
366
+ final status = Status(
367
+ status: 'running',
368
+ version: '1.0.0',
369
+ directoriesCount: 1,
370
+ host: 'localhost',
371
+ port: 5000,
372
+ uptimeSeconds: 3599,
373
+ );
374
+ expect(status.uptimeFormatted, '59m 59s');
375
+ });
376
+
377
+ test('formats exactly 1 hour', () {
378
+ final status = Status(
379
+ status: 'running',
380
+ version: '1.0.0',
381
+ directoriesCount: 1,
382
+ host: 'localhost',
383
+ port: 5000,
384
+ uptimeSeconds: 3600,
385
+ );
386
+ expect(status.uptimeFormatted, '1h 0m 0s');
387
+ });
388
+
389
+ test('formats hours, minutes, and seconds', () {
390
+ final status = Status(
391
+ status: 'running',
392
+ version: '1.0.0',
393
+ directoriesCount: 1,
394
+ host: 'localhost',
395
+ port: 5000,
396
+ uptimeSeconds: 3665,
397
+ );
398
+ expect(status.uptimeFormatted, '1h 1m 5s');
399
+ });
400
+
401
+ test('formats 23 hours 59 minutes 59 seconds', () {
402
+ final status = Status(
403
+ status: 'running',
404
+ version: '1.0.0',
405
+ directoriesCount: 1,
406
+ host: 'localhost',
407
+ port: 5000,
408
+ uptimeSeconds: 86399,
409
+ );
410
+ expect(status.uptimeFormatted, '23h 59m 59s');
411
+ });
412
+
413
+ test('formats exactly 1 day', () {
414
+ final status = Status(
415
+ status: 'running',
416
+ version: '1.0.0',
417
+ directoriesCount: 1,
418
+ host: 'localhost',
419
+ port: 5000,
420
+ uptimeSeconds: 86400,
421
+ );
422
+ expect(status.uptimeFormatted, '1d 0h 0m');
423
+ });
424
+
425
+ test('formats days, hours, and minutes', () {
426
+ final status = Status(
427
+ status: 'running',
428
+ version: '1.0.0',
429
+ directoriesCount: 1,
430
+ host: 'localhost',
431
+ port: 5000,
432
+ uptimeSeconds: 90061,
433
+ );
434
+ expect(status.uptimeFormatted, '1d 1h 1m');
435
+ });
436
+
437
+ test('formats multiple days', () {
438
+ final status = Status(
439
+ status: 'running',
440
+ version: '1.0.0',
441
+ directoriesCount: 1,
442
+ host: 'localhost',
443
+ port: 5000,
444
+ uptimeSeconds: 172800,
445
+ );
446
+ expect(status.uptimeFormatted, '2d 0h 0m');
447
+ });
448
+
449
+ test('formats 7 days', () {
450
+ final status = Status(
451
+ status: 'running',
452
+ version: '1.0.0',
453
+ directoriesCount: 1,
454
+ host: 'localhost',
455
+ port: 5000,
456
+ uptimeSeconds: 604800,
457
+ );
458
+ expect(status.uptimeFormatted, '7d 0h 0m');
459
+ });
460
+
461
+ test('formats 30 days with hours and minutes', () {
462
+ final status = Status(
463
+ status: 'running',
464
+ version: '1.0.0',
465
+ directoriesCount: 1,
466
+ host: 'localhost',
467
+ port: 5000,
468
+ uptimeSeconds: 2595665, // 30 days + 1 hour + 1 minute + 5 seconds
469
+ );
470
+ expect(status.uptimeFormatted, '30d 1h 1m');
471
+ });
472
+
473
+ test('formats 365 days', () {
474
+ final status = Status(
475
+ status: 'running',
476
+ version: '1.0.0',
477
+ directoriesCount: 1,
478
+ host: 'localhost',
479
+ port: 5000,
480
+ uptimeSeconds: 31536000,
481
+ );
482
+ expect(status.uptimeFormatted, '365d 0h 0m');
483
+ });
484
+
485
+ test('formats zero seconds', () {
486
+ final status = Status(
487
+ status: 'running',
488
+ version: '1.0.0',
489
+ directoriesCount: 1,
490
+ host: 'localhost',
491
+ port: 5000,
492
+ uptimeSeconds: 0,
493
+ );
494
+ expect(status.uptimeFormatted, '0s');
495
+ });
496
+
497
+ test('omits seconds in day format', () {
498
+ final status = Status(
499
+ status: 'running',
500
+ version: '1.0.0',
501
+ directoriesCount: 1,
502
+ host: 'localhost',
503
+ port: 5000,
504
+ uptimeSeconds: 90065, // 1d 1h 1m 5s
505
+ );
506
+ expect(status.uptimeFormatted, '1d 1h 1m');
507
+ expect(status.uptimeFormatted, isNot(contains('s')));
508
+ });
509
+
510
+ test('shows seconds in hour format', () {
511
+ final status = Status(
512
+ status: 'running',
513
+ version: '1.0.0',
514
+ directoriesCount: 1,
515
+ host: 'localhost',
516
+ port: 5000,
517
+ uptimeSeconds: 3665, // 1h 1m 5s
518
+ );
519
+ expect(status.uptimeFormatted, '1h 1m 5s');
520
+ expect(status.uptimeFormatted, contains('5s'));
521
+ });
522
+ });
523
+
524
+ group('Special Characters and Edge Cases', () {
525
+ test('handles version with pre-release info', () {
526
+ final status = Status(
527
+ status: 'running',
528
+ version: '1.2.3-beta.1',
529
+ directoriesCount: 1,
530
+ host: 'localhost',
531
+ port: 5000,
532
+ uptimeSeconds: 100,
533
+ );
534
+
535
+ expect(status.version, '1.2.3-beta.1');
536
+ });
537
+
538
+ test('handles version with build metadata', () {
539
+ final status = Status(
540
+ status: 'running',
541
+ version: '1.2.3+build.456',
542
+ directoriesCount: 1,
543
+ host: 'localhost',
544
+ port: 5000,
545
+ uptimeSeconds: 100,
546
+ );
547
+
548
+ expect(status.version, '1.2.3+build.456');
549
+ });
550
+
551
+ test('handles hostname with special characters', () {
552
+ final status = Status(
553
+ status: 'running',
554
+ version: '1.0.0',
555
+ directoriesCount: 1,
556
+ host: 'my-server.local',
557
+ port: 5000,
558
+ uptimeSeconds: 100,
559
+ );
560
+
561
+ expect(status.host, 'my-server.local');
562
+ });
563
+
564
+ test('handles IPv6 address', () {
565
+ final status = Status(
566
+ status: 'running',
567
+ version: '1.0.0',
568
+ directoriesCount: 1,
569
+ host: '::1',
570
+ port: 5000,
571
+ uptimeSeconds: 100,
572
+ );
573
+
574
+ expect(status.host, '::1');
575
+ });
576
+
577
+ test('handles very large uptime', () {
578
+ final status = Status(
579
+ status: 'running',
580
+ version: '1.0.0',
581
+ directoriesCount: 1,
582
+ host: 'localhost',
583
+ port: 5000,
584
+ uptimeSeconds: 999999999,
585
+ );
586
+
587
+ expect(status.uptimeSeconds, 999999999);
588
+ expect(status.uptimeFormatted, contains('d'));
589
+ });
590
+
591
+ test('handles large directory count', () {
592
+ final status = Status(
593
+ status: 'running',
594
+ version: '1.0.0',
595
+ directoriesCount: 1000,
596
+ host: 'localhost',
597
+ port: 5000,
598
+ uptimeSeconds: 100,
599
+ );
600
+
601
+ expect(status.directoriesCount, 1000);
602
+ });
603
+
604
+ test('handles negative values', () {
605
+ final status = Status(
606
+ status: 'running',
607
+ version: '1.0.0',
608
+ directoriesCount: -1,
609
+ host: 'localhost',
610
+ port: -1,
611
+ uptimeSeconds: -1,
612
+ );
613
+
614
+ expect(status.directoriesCount, -1);
615
+ expect(status.port, -1);
616
+ expect(status.uptimeSeconds, -1);
617
+ });
618
+ });
619
+
620
+ group('Real-World Scenarios', () {
621
+ test('creates status from typical API response', () {
622
+ final json = {
623
+ 'status': 'running',
624
+ 'version': '0.9.0',
625
+ 'directories_count': 3,
626
+ 'host': 'pumaguard.local',
627
+ 'port': 5000,
628
+ 'uptime_seconds': 7200,
629
+ };
630
+
631
+ final status = Status.fromJson(json);
632
+
633
+ expect(status.status, 'running');
634
+ expect(status.isRunning, true);
635
+ expect(status.version, '0.9.0');
636
+ expect(status.directoriesCount, 3);
637
+ expect(status.uptimeFormatted, '2h 0m 0s');
638
+ });
639
+
640
+ test('creates stopped status', () {
641
+ final json = {
642
+ 'status': 'stopped',
643
+ 'version': '1.0.0',
644
+ 'directories_count': 0,
645
+ 'host': 'localhost',
646
+ 'port': 5000,
647
+ 'uptime_seconds': 0,
648
+ };
649
+
650
+ final status = Status.fromJson(json);
651
+
652
+ expect(status.isRunning, false);
653
+ expect(status.status, 'stopped');
654
+ expect(status.uptimeFormatted, '0s');
655
+ });
656
+
657
+ test('creates status with long uptime', () {
658
+ final json = {
659
+ 'status': 'running',
660
+ 'version': '1.0.0',
661
+ 'directories_count': 5,
662
+ 'host': '192.168.52.100',
663
+ 'port': 8080,
664
+ 'uptime_seconds': 2592000, // 30 days
665
+ };
666
+
667
+ final status = Status.fromJson(json);
668
+
669
+ expect(status.isRunning, true);
670
+ expect(status.uptimeFormatted, '30d 0h 0m');
671
+ });
672
+
673
+ test('creates status with custom port', () {
674
+ final json = {
675
+ 'status': 'running',
676
+ 'version': '1.0.0',
677
+ 'directories_count': 2,
678
+ 'host': '0.0.0.0',
679
+ 'port': 8080,
680
+ 'uptime_seconds': 300,
681
+ };
682
+
683
+ final status = Status.fromJson(json);
684
+
685
+ expect(status.port, 8080);
686
+ expect(status.host, '0.0.0.0');
687
+ expect(status.uptimeFormatted, '5m 0s');
688
+ });
689
+
690
+ test('creates status with mDNS hostname', () {
691
+ final json = {
692
+ 'status': 'running',
693
+ 'version': '1.0.0',
694
+ 'directories_count': 1,
695
+ 'host': 'pumaguard.local',
696
+ 'port': 5000,
697
+ 'uptime_seconds': 86400,
698
+ };
699
+
700
+ final status = Status.fromJson(json);
701
+
702
+ expect(status.host, 'pumaguard.local');
703
+ expect(status.uptimeFormatted, '1d 0h 0m');
704
+ });
705
+ });
706
+ });
707
+ }