Add unit tests for EconomicCustomerModel parsing and validation

This commit is contained in:
Jeppe Bundgaard
2026-04-21 15:07:41 +02:00
parent 7d450e285e
commit 0b18433fdf
11 changed files with 1070 additions and 26 deletions
@@ -9849,3 +9849,719 @@
[Tue Apr 21 10:21:42 2026] 127.0.0.1:40134 Closing
[Tue Apr 21 10:21:42 2026] 127.0.0.1:60180 Accepted
[Tue Apr 21 10:21:46 2026] 127.0.0.1:60180 Closing
[Tue Apr 21 12:19:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:26 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:26 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:27 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:28 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:28 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:29 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:30 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:31 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:32 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:34 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:34 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:36 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:36 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:38 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:38 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:40 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:40 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:44 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:45 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:46 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:47 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:48 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:49 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:50 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:51 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:52 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:52 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:53 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:53 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:54 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:55 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:19:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:20:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:56 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:57 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:58 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:21:59 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:00 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:22:01 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:21 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:21 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:22 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:23 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:24 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:23:25 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:03 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:04 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:05 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:06 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:07 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:08 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:25:08 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:28:04 2026] PHP 8.2.30 Development Server (http://127.0.0.1:18083) started
[Tue Apr 21 12:28:04 2026] 127.0.0.1:36832 Accepted
[Tue Apr 21 12:28:06 2026] 127.0.0.1:36832 Closing
[Tue Apr 21 12:28:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:28:42 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:29:18 2026] Failed to listen on 127.0.0.1:18080 (reason: Address already in use)
[Tue Apr 21 12:29:38 2026] PHP 8.2.30 Development Server (http://127.0.0.1:18084) started
[Tue Apr 21 12:29:39 2026] 127.0.0.1:49800 Accepted
[Tue Apr 21 12:29:41 2026] 127.0.0.1:49800 Closing
[Tue Apr 21 12:31:10 2026] PHP 8.2.30 Development Server (http://127.0.0.1:33655) started
[Tue Apr 21 12:31:10 2026] 127.0.0.1:37066 Accepted
[Tue Apr 21 12:31:09 2026] 127.0.0.1:37066 Closing
[Tue Apr 21 12:31:09 2026] 127.0.0.1:37070 Accepted
[Tue Apr 21 12:31:10 2026] 127.0.0.1:37070 Closing
[Tue Apr 21 12:31:30 2026] PHP 8.2.30 Development Server (http://127.0.0.1:33509) started
[Tue Apr 21 12:31:30 2026] 127.0.0.1:57784 Accepted
[Tue Apr 21 12:31:31 2026] 127.0.0.1:57784 Closing
[Tue Apr 21 12:31:31 2026] 127.0.0.1:57788 Accepted
[Tue Apr 21 12:31:32 2026] 127.0.0.1:57788 Closing
[Tue Apr 21 12:31:33 2026] 127.0.0.1:59232 Accepted
[Tue Apr 21 12:31:33 2026] 127.0.0.1:59232 Closing
[Tue Apr 21 12:31:33 2026] 127.0.0.1:59246 Accepted
[Tue Apr 21 12:31:34 2026] 127.0.0.1:59246 Closing
[Tue Apr 21 12:31:34 2026] 127.0.0.1:59250 Accepted
[Tue Apr 21 12:31:35 2026] 127.0.0.1:59250 Closing
[Tue Apr 21 12:31:35 2026] 127.0.0.1:59260 Accepted
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59260 Closing
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59266 Accepted
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59266 Closing
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59274 Accepted
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59274 Closing
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59282 Accepted
[Tue Apr 21 12:31:38 2026] 127.0.0.1:59282 Closing
[Tue Apr 21 12:31:38 2026] 127.0.0.1:59292 Accepted
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59292 Closing
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59298 Accepted
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59298 Closing
[Tue Apr 21 12:31:36 2026] 127.0.0.1:59314 Accepted
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59314 Closing
[Tue Apr 21 12:31:37 2026] 127.0.0.1:59316 Accepted
[Tue Apr 21 12:31:38 2026] 127.0.0.1:59316 Closing
[Tue Apr 21 12:32:49 2026] PHP 8.2.30 Development Server (http://127.0.0.1:40003) started
[Tue Apr 21 12:32:50 2026] 127.0.0.1:56156 Accepted
[Tue Apr 21 12:32:51 2026] 127.0.0.1:56156 Closing
[Tue Apr 21 12:32:51 2026] 127.0.0.1:56158 Accepted
[Tue Apr 21 12:32:51 2026] 127.0.0.1:56158 Closing
[Tue Apr 21 12:32:52 2026] 127.0.0.1:56174 Accepted
[Tue Apr 21 12:32:52 2026] 127.0.0.1:56174 Closing
[Tue Apr 21 12:32:52 2026] 127.0.0.1:56178 Accepted
[Tue Apr 21 12:32:53 2026] 127.0.0.1:56178 Closing
[Tue Apr 21 12:32:53 2026] 127.0.0.1:56192 Accepted
[Tue Apr 21 12:32:53 2026] 127.0.0.1:56192 Closing
[Tue Apr 21 12:32:54 2026] 127.0.0.1:56196 Accepted
[Tue Apr 21 12:32:54 2026] 127.0.0.1:56196 Closing
[Tue Apr 21 12:32:55 2026] 127.0.0.1:59960 Accepted
[Tue Apr 21 12:32:55 2026] 127.0.0.1:59960 Closing
[Tue Apr 21 12:32:55 2026] 127.0.0.1:59964 Accepted
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59964 Closing
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59972 Accepted
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59972 Closing
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59982 Accepted
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59982 Closing
[Tue Apr 21 12:32:56 2026] 127.0.0.1:59984 Accepted
[Tue Apr 21 12:32:57 2026] 127.0.0.1:59984 Closing
[Tue Apr 21 12:32:57 2026] 127.0.0.1:59994 Accepted
[Tue Apr 21 12:32:57 2026] 127.0.0.1:59994 Closing
[Tue Apr 21 12:32:57 2026] 127.0.0.1:60002 Accepted
[Tue Apr 21 12:32:58 2026] 127.0.0.1:60002 Closing
[Tue Apr 21 12:34:41 2026] PHP 8.2.30 Development Server (http://127.0.0.1:37999) started
[Tue Apr 21 12:34:41 2026] 127.0.0.1:42608 Accepted
[Tue Apr 21 12:34:42 2026] 127.0.0.1:42608 Closing
[Tue Apr 21 12:34:42 2026] 127.0.0.1:42610 Accepted
[Tue Apr 21 12:34:43 2026] 127.0.0.1:42610 Closing
[Tue Apr 21 12:34:43 2026] 127.0.0.1:42626 Accepted
[Tue Apr 21 12:34:44 2026] 127.0.0.1:42626 Closing
[Tue Apr 21 12:34:44 2026] 127.0.0.1:59522 Accepted
[Tue Apr 21 12:34:44 2026] 127.0.0.1:59522 Closing
[Tue Apr 21 12:34:44 2026] 127.0.0.1:59532 Accepted
[Tue Apr 21 12:34:45 2026] 127.0.0.1:59532 Closing
[Tue Apr 21 12:34:45 2026] 127.0.0.1:59544 Accepted
[Tue Apr 21 12:34:46 2026] 127.0.0.1:59544 Closing
[Tue Apr 21 12:34:46 2026] 127.0.0.1:59554 Accepted
[Tue Apr 21 12:34:47 2026] 127.0.0.1:59554 Closing
[Tue Apr 21 12:34:47 2026] 127.0.0.1:59568 Accepted
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59568 Closing
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59582 Accepted
[Tue Apr 21 12:34:49 2026] 127.0.0.1:59582 Closing
[Tue Apr 21 12:34:49 2026] 127.0.0.1:59594 Accepted
[Tue Apr 21 12:34:47 2026] 127.0.0.1:59594 Closing
[Tue Apr 21 12:34:47 2026] 127.0.0.1:59602 Accepted
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59602 Closing
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59608 Accepted
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59608 Closing
[Tue Apr 21 12:34:48 2026] 127.0.0.1:59622 Accepted
[Tue Apr 21 12:34:49 2026] 127.0.0.1:59622 Closing
[Tue Apr 21 12:38:02 2026] PHP 8.2.30 Development Server (http://127.0.0.1:35141) started
[Tue Apr 21 12:38:02 2026] 127.0.0.1:56092 Accepted
[Tue Apr 21 12:38:03 2026] 127.0.0.1:56092 Closing
[Tue Apr 21 12:38:03 2026] 127.0.0.1:56094 Accepted
[Tue Apr 21 12:38:04 2026] 127.0.0.1:56094 Closing
[Tue Apr 21 12:38:05 2026] 127.0.0.1:56108 Accepted
[Tue Apr 21 12:38:05 2026] 127.0.0.1:56108 Closing
[Tue Apr 21 12:38:05 2026] 127.0.0.1:56118 Accepted
[Tue Apr 21 12:38:06 2026] 127.0.0.1:56118 Closing
[Tue Apr 21 12:38:06 2026] 127.0.0.1:56134 Accepted
[Tue Apr 21 12:38:06 2026] 127.0.0.1:56134 Closing
[Tue Apr 21 12:38:07 2026] 127.0.0.1:56136 Accepted
[Tue Apr 21 12:38:07 2026] 127.0.0.1:56136 Closing
[Tue Apr 21 12:38:07 2026] 127.0.0.1:56144 Accepted
[Tue Apr 21 12:38:07 2026] 127.0.0.1:56144 Closing
[Tue Apr 21 12:38:07 2026] 127.0.0.1:56156 Accepted
[Tue Apr 21 12:38:08 2026] 127.0.0.1:56156 Closing
[Tue Apr 21 12:38:08 2026] 127.0.0.1:56166 Accepted
[Tue Apr 21 12:38:08 2026] 127.0.0.1:56166 Closing
[Tue Apr 21 12:38:08 2026] 127.0.0.1:56172 Accepted
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56172 Closing
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56186 Accepted
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56186 Closing
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56188 Accepted
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56188 Closing
[Tue Apr 21 12:38:09 2026] 127.0.0.1:56196 Accepted
[Tue Apr 21 12:38:10 2026] 127.0.0.1:56196 Closing
[Tue Apr 21 12:39:00 2026] PHP 8.2.30 Development Server (http://127.0.0.1:40709) started
[Tue Apr 21 12:39:00 2026] 127.0.0.1:52258 Accepted
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52258 Closing
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52268 Accepted
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52268 Closing
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52270 Accepted
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52270 Closing
[Tue Apr 21 12:39:01 2026] 127.0.0.1:52286 Accepted
[Tue Apr 21 12:39:02 2026] 127.0.0.1:52286 Closing
[Tue Apr 21 12:39:02 2026] 127.0.0.1:52290 Accepted
[Tue Apr 21 12:39:02 2026] 127.0.0.1:52290 Closing
[Tue Apr 21 12:39:02 2026] 127.0.0.1:52298 Accepted
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52298 Closing
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52300 Accepted
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52300 Closing
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52312 Accepted
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52312 Closing
[Tue Apr 21 12:39:03 2026] 127.0.0.1:52324 Accepted
[Tue Apr 21 12:39:04 2026] 127.0.0.1:52324 Closing
[Tue Apr 21 12:39:04 2026] 127.0.0.1:52326 Accepted
[Tue Apr 21 12:39:04 2026] 127.0.0.1:52326 Closing
[Tue Apr 21 12:39:04 2026] 127.0.0.1:52328 Accepted
[Tue Apr 21 12:39:05 2026] 127.0.0.1:52328 Closing
[Tue Apr 21 12:39:05 2026] 127.0.0.1:52330 Accepted
[Tue Apr 21 12:39:06 2026] 127.0.0.1:52330 Closing
[Tue Apr 21 12:39:06 2026] 127.0.0.1:52346 Accepted
[Tue Apr 21 12:39:06 2026] 127.0.0.1:52346 Closing
[Tue Apr 21 12:39:06 2026] 127.0.0.1:49132 Accepted
[Tue Apr 21 12:39:07 2026] 127.0.0.1:49132 Closing
[Tue Apr 21 12:39:07 2026] 127.0.0.1:49142 Accepted
[Tue Apr 21 12:39:07 2026] 127.0.0.1:49142 Closing
[Tue Apr 21 12:39:07 2026] 127.0.0.1:49150 Accepted
[Tue Apr 21 12:39:08 2026] 127.0.0.1:49150 Closing
[Tue Apr 21 12:39:08 2026] 127.0.0.1:49160 Accepted
[Tue Apr 21 12:39:08 2026] 127.0.0.1:49160 Closing
[Tue Apr 21 12:39:08 2026] 127.0.0.1:49176 Accepted
[Tue Apr 21 12:39:09 2026] 127.0.0.1:49176 Closing
[Tue Apr 21 12:39:09 2026] 127.0.0.1:49182 Accepted
[Tue Apr 21 12:39:10 2026] 127.0.0.1:49182 Closing
[Tue Apr 21 12:39:10 2026] 127.0.0.1:49194 Accepted
[Tue Apr 21 12:39:11 2026] 127.0.0.1:49194 Closing
[Tue Apr 21 12:39:11 2026] 127.0.0.1:49196 Accepted
[Tue Apr 21 12:39:11 2026] 127.0.0.1:49196 Closing
[Tue Apr 21 12:39:11 2026] 127.0.0.1:49210 Accepted
[Tue Apr 21 12:39:12 2026] 127.0.0.1:49210 Closing
[Tue Apr 21 12:39:12 2026] 127.0.0.1:49216 Accepted
[Tue Apr 21 12:39:13 2026] 127.0.0.1:49216 Closing
[Tue Apr 21 12:39:13 2026] 127.0.0.1:49232 Accepted
[Tue Apr 21 12:39:13 2026] 127.0.0.1:49232 Closing
[Tue Apr 21 12:39:13 2026] 127.0.0.1:49234 Accepted
[Tue Apr 21 12:39:14 2026] 127.0.0.1:49234 Closing
[Tue Apr 21 12:39:14 2026] 127.0.0.1:49238 Accepted
[Tue Apr 21 12:39:14 2026] 127.0.0.1:49238 Closing
[Tue Apr 21 12:39:14 2026] 127.0.0.1:49252 Accepted
[Tue Apr 21 12:39:15 2026] 127.0.0.1:49252 Closing
[Tue Apr 21 12:39:15 2026] 127.0.0.1:49262 Accepted
[Tue Apr 21 12:39:15 2026] 127.0.0.1:49262 Closing
[Tue Apr 21 12:39:15 2026] 127.0.0.1:49266 Accepted
[Tue Apr 21 12:39:16 2026] 127.0.0.1:49266 Closing
[Tue Apr 21 12:39:16 2026] 127.0.0.1:49278 Accepted
[Tue Apr 21 12:39:16 2026] 127.0.0.1:49278 Closing
[Tue Apr 21 12:39:16 2026] 127.0.0.1:49280 Accepted
[Tue Apr 21 12:39:16 2026] 127.0.0.1:49280 Closing
[Tue Apr 21 12:39:17 2026] 127.0.0.1:44144 Accepted
[Tue Apr 21 12:39:17 2026] 127.0.0.1:44144 Closing
[Tue Apr 21 12:39:17 2026] 127.0.0.1:44160 Accepted
[Tue Apr 21 12:39:18 2026] 127.0.0.1:44160 Closing
[Tue Apr 21 12:39:18 2026] 127.0.0.1:44168 Accepted
[Tue Apr 21 12:39:18 2026] 127.0.0.1:44168 Closing
[Tue Apr 21 12:39:18 2026] 127.0.0.1:44178 Accepted
[Tue Apr 21 12:39:19 2026] 127.0.0.1:44178 Closing
[Tue Apr 21 12:39:19 2026] 127.0.0.1:44182 Accepted
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44182 Closing
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44190 Accepted
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44190 Closing
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44206 Accepted
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44206 Closing
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44214 Accepted
[Tue Apr 21 12:39:22 2026] 127.0.0.1:44214 Closing
[Tue Apr 21 12:39:22 2026] 127.0.0.1:44220 Accepted
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44220 Closing
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44234 Accepted
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44234 Closing
[Tue Apr 21 12:39:20 2026] 127.0.0.1:44244 Accepted
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44244 Closing
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44260 Accepted
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44260 Closing
[Tue Apr 21 12:39:21 2026] 127.0.0.1:44272 Accepted
[Tue Apr 21 12:39:23 2026] 127.0.0.1:44272 Closing
[Tue Apr 21 12:39:23 2026] 127.0.0.1:44280 Accepted
[Tue Apr 21 12:39:24 2026] 127.0.0.1:44280 Closing
[Tue Apr 21 12:39:24 2026] 127.0.0.1:34634 Accepted
[Tue Apr 21 12:39:24 2026] 127.0.0.1:34634 Closing
[Tue Apr 21 12:39:24 2026] 127.0.0.1:34644 Accepted
[Tue Apr 21 12:39:25 2026] 127.0.0.1:34644 Closing
[Tue Apr 21 12:39:25 2026] 127.0.0.1:34658 Accepted
[Tue Apr 21 12:39:25 2026] 127.0.0.1:34658 Closing
[Tue Apr 21 12:39:25 2026] 127.0.0.1:34674 Accepted
[Tue Apr 21 12:39:26 2026] 127.0.0.1:34674 Closing
[Tue Apr 21 12:39:26 2026] 127.0.0.1:34688 Accepted
[Tue Apr 21 12:39:26 2026] 127.0.0.1:34688 Closing
[Tue Apr 21 12:39:26 2026] 127.0.0.1:34696 Accepted
[Tue Apr 21 12:39:27 2026] 127.0.0.1:34696 Closing
[Tue Apr 21 12:39:27 2026] 127.0.0.1:34702 Accepted
[Tue Apr 21 12:39:28 2026] 127.0.0.1:34702 Closing
[Tue Apr 21 12:39:28 2026] 127.0.0.1:34712 Accepted
[Tue Apr 21 12:39:29 2026] 127.0.0.1:34712 Closing
[Tue Apr 21 12:39:29 2026] 127.0.0.1:34726 Accepted
[Tue Apr 21 12:39:32 2026] 127.0.0.1:34726 Closing
[Tue Apr 21 12:39:32 2026] 127.0.0.1:34742 Accepted
[Tue Apr 21 12:39:34 2026] 127.0.0.1:34742 Closing
[Tue Apr 21 12:39:34 2026] 127.0.0.1:43282 Accepted
[Tue Apr 21 12:39:35 2026] 127.0.0.1:43282 Closing
[Tue Apr 21 12:39:35 2026] 127.0.0.1:43286 Accepted
[Tue Apr 21 12:39:36 2026] 127.0.0.1:43286 Closing
[Tue Apr 21 12:39:36 2026] 127.0.0.1:43290 Accepted
[Tue Apr 21 12:39:36 2026] 127.0.0.1:43290 Closing
[Tue Apr 21 12:39:36 2026] 127.0.0.1:43302 Accepted
[Tue Apr 21 12:39:37 2026] 127.0.0.1:43302 Closing
[Tue Apr 21 12:39:37 2026] 127.0.0.1:43310 Accepted
[Tue Apr 21 12:39:38 2026] 127.0.0.1:43310 Closing
[Tue Apr 21 12:39:38 2026] 127.0.0.1:43312 Accepted
[Tue Apr 21 12:39:39 2026] 127.0.0.1:43312 Closing
[Tue Apr 21 12:39:39 2026] 127.0.0.1:43322 Accepted
[Tue Apr 21 12:39:40 2026] 127.0.0.1:43322 Closing
[Tue Apr 21 12:39:40 2026] 127.0.0.1:43334 Accepted
[Tue Apr 21 12:39:40 2026] 127.0.0.1:43334 Closing
[Tue Apr 21 12:39:40 2026] 127.0.0.1:43336 Accepted
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43336 Closing
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43340 Accepted
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43340 Closing
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43346 Accepted
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43346 Closing
[Tue Apr 21 12:39:41 2026] 127.0.0.1:43362 Accepted
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43362 Closing
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43364 Accepted
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43364 Closing
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43370 Accepted
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43370 Closing
[Tue Apr 21 12:39:42 2026] 127.0.0.1:43386 Accepted
[Tue Apr 21 12:39:43 2026] 127.0.0.1:43386 Closing
[Tue Apr 21 12:39:43 2026] 127.0.0.1:43392 Accepted
[Tue Apr 21 12:39:43 2026] 127.0.0.1:43392 Closing
[Tue Apr 21 12:39:43 2026] 127.0.0.1:43404 Accepted
[Tue Apr 21 12:39:44 2026] 127.0.0.1:43404 Closing
[Tue Apr 21 12:39:44 2026] 127.0.0.1:41558 Accepted
[Tue Apr 21 12:39:44 2026] 127.0.0.1:41558 Closing
[Tue Apr 21 12:39:45 2026] 127.0.0.1:41564 Accepted
[Tue Apr 21 12:39:45 2026] 127.0.0.1:41564 Closing
[Tue Apr 21 12:39:45 2026] 127.0.0.1:41566 Accepted
[Tue Apr 21 12:39:46 2026] 127.0.0.1:41566 Closing
[Tue Apr 21 12:39:46 2026] 127.0.0.1:41570 Accepted
[Tue Apr 21 12:39:47 2026] 127.0.0.1:41570 Closing
[Tue Apr 21 12:39:47 2026] 127.0.0.1:41584 Accepted
[Tue Apr 21 12:39:47 2026] 127.0.0.1:41584 Closing
[Tue Apr 21 12:39:48 2026] 127.0.0.1:41586 Accepted
[Tue Apr 21 12:39:49 2026] 127.0.0.1:41586 Closing
[Tue Apr 21 12:39:49 2026] 127.0.0.1:41602 Accepted
[Tue Apr 21 12:39:49 2026] 127.0.0.1:41602 Closing
[Tue Apr 21 12:48:25 2026] PHP 8.2.30 Development Server (http://127.0.0.1:37693) started
[Tue Apr 21 12:48:26 2026] 127.0.0.1:56166 Accepted
[Tue Apr 21 12:48:26 2026] 127.0.0.1:56166 Closing
[Tue Apr 21 12:48:26 2026] 127.0.0.1:56182 Accepted
[Tue Apr 21 12:48:27 2026] 127.0.0.1:56182 Closing
[Tue Apr 21 12:48:27 2026] 127.0.0.1:56186 Accepted
[Tue Apr 21 12:48:29 2026] 127.0.0.1:56186 Closing
[Tue Apr 21 12:48:30 2026] 127.0.0.1:52486 Accepted
[Tue Apr 21 12:48:31 2026] 127.0.0.1:52486 Closing
[Tue Apr 21 12:48:31 2026] 127.0.0.1:52498 Accepted
[Tue Apr 21 12:48:32 2026] 127.0.0.1:52498 Closing
[Tue Apr 21 12:48:32 2026] 127.0.0.1:52508 Accepted
[Tue Apr 21 12:48:33 2026] 127.0.0.1:52508 Closing
[Tue Apr 21 12:48:33 2026] 127.0.0.1:52518 Accepted
[Tue Apr 21 12:48:34 2026] 127.0.0.1:52518 Closing
[Tue Apr 21 12:48:34 2026] 127.0.0.1:52532 Accepted
[Tue Apr 21 12:48:36 2026] 127.0.0.1:52532 Closing
[Tue Apr 21 12:48:36 2026] 127.0.0.1:52548 Accepted
[Tue Apr 21 12:48:37 2026] 127.0.0.1:52548 Closing
[Tue Apr 21 12:48:37 2026] 127.0.0.1:52560 Accepted
[Tue Apr 21 12:48:38 2026] 127.0.0.1:52560 Closing
[Tue Apr 21 12:48:38 2026] 127.0.0.1:52576 Accepted
[Tue Apr 21 12:48:39 2026] 127.0.0.1:52576 Closing
[Tue Apr 21 12:48:39 2026] 127.0.0.1:57582 Accepted
[Tue Apr 21 12:48:40 2026] 127.0.0.1:57582 Closing
[Tue Apr 21 12:48:40 2026] 127.0.0.1:57598 Accepted
[Tue Apr 21 12:48:41 2026] 127.0.0.1:57598 Closing
[Tue Apr 21 12:48:41 2026] 127.0.0.1:57614 Accepted
[Tue Apr 21 12:48:42 2026] 127.0.0.1:57614 Closing
[Tue Apr 21 12:48:42 2026] 127.0.0.1:57624 Accepted
[Tue Apr 21 12:48:43 2026] 127.0.0.1:57624 Closing
[Tue Apr 21 12:48:43 2026] 127.0.0.1:57628 Accepted
[Tue Apr 21 12:48:46 2026] 127.0.0.1:57628 Closing
[Tue Apr 21 12:48:46 2026] 127.0.0.1:57634 Accepted
[Tue Apr 21 12:48:46 2026] 127.0.0.1:57634 Closing
[Tue Apr 21 12:48:46 2026] 127.0.0.1:57646 Accepted
[Tue Apr 21 12:48:48 2026] 127.0.0.1:57646 Closing
[Tue Apr 21 12:49:51 2026] PHP 8.2.30 Development Server (http://127.0.0.1:41511) started
[Tue Apr 21 12:49:51 2026] 127.0.0.1:60470 Accepted
[Tue Apr 21 12:49:53 2026] 127.0.0.1:60470 Closing
[Tue Apr 21 12:49:53 2026] 127.0.0.1:60472 Accepted
[Tue Apr 21 12:49:53 2026] 127.0.0.1:60472 Closing
[Tue Apr 21 12:49:54 2026] 127.0.0.1:60480 Accepted
[Tue Apr 21 12:49:57 2026] 127.0.0.1:60480 Closing
[Tue Apr 21 12:49:57 2026] 127.0.0.1:60484 Accepted
[Tue Apr 21 12:49:59 2026] 127.0.0.1:60484 Closing
[Tue Apr 21 12:49:59 2026] 127.0.0.1:60494 Accepted
[Tue Apr 21 12:50:00 2026] 127.0.0.1:60494 Closing
[Tue Apr 21 12:50:00 2026] 127.0.0.1:60504 Accepted
[Tue Apr 21 12:50:02 2026] 127.0.0.1:60504 Closing
[Tue Apr 21 12:50:02 2026] 127.0.0.1:50656 Accepted
[Tue Apr 21 12:50:03 2026] 127.0.0.1:50656 Closing
[Tue Apr 21 12:50:04 2026] 127.0.0.1:50670 Accepted
[Tue Apr 21 12:50:05 2026] 127.0.0.1:50670 Closing
[Tue Apr 21 12:50:05 2026] 127.0.0.1:50676 Accepted
[Tue Apr 21 12:50:07 2026] 127.0.0.1:50676 Closing
[Tue Apr 21 12:50:07 2026] 127.0.0.1:50678 Accepted
[Tue Apr 21 12:50:09 2026] 127.0.0.1:50678 Closing
[Tue Apr 21 12:50:09 2026] 127.0.0.1:50686 Accepted
[Tue Apr 21 12:50:10 2026] 127.0.0.1:50686 Closing
[Tue Apr 21 12:50:10 2026] 127.0.0.1:50694 Accepted
[Tue Apr 21 12:50:11 2026] 127.0.0.1:50694 Closing
[Tue Apr 21 12:51:35 2026] PHP 8.2.30 Development Server (http://127.0.0.1:44745) started
[Tue Apr 21 12:51:35 2026] 127.0.0.1:56336 Accepted
[Tue Apr 21 12:51:36 2026] 127.0.0.1:56336 Closing
[Tue Apr 21 12:51:36 2026] 127.0.0.1:56342 Accepted
[Tue Apr 21 12:51:37 2026] 127.0.0.1:56342 Closing
[Tue Apr 21 12:51:37 2026] 127.0.0.1:56344 Accepted
[Tue Apr 21 12:51:37 2026] 127.0.0.1:56344 Closing
[Tue Apr 21 12:51:39 2026] 127.0.0.1:56354 Accepted
[Tue Apr 21 12:51:41 2026] 127.0.0.1:56354 Closing
[Tue Apr 21 12:51:41 2026] 127.0.0.1:37198 Accepted
[Tue Apr 21 12:51:42 2026] 127.0.0.1:37198 Closing
[Tue Apr 21 12:51:42 2026] 127.0.0.1:37212 Accepted
[Tue Apr 21 12:51:44 2026] 127.0.0.1:37212 Closing
[Tue Apr 21 12:51:44 2026] 127.0.0.1:37228 Accepted
[Tue Apr 21 12:51:46 2026] 127.0.0.1:37228 Closing
[Tue Apr 21 12:51:46 2026] 127.0.0.1:37236 Accepted
[Tue Apr 21 12:51:47 2026] 127.0.0.1:37236 Closing
[Tue Apr 21 12:51:47 2026] 127.0.0.1:37244 Accepted
[Tue Apr 21 12:51:49 2026] 127.0.0.1:37244 Closing
[Tue Apr 21 12:51:49 2026] 127.0.0.1:37252 Accepted
[Tue Apr 21 12:51:50 2026] 127.0.0.1:37252 Closing
[Tue Apr 21 12:51:50 2026] 127.0.0.1:52198 Accepted
[Tue Apr 21 12:51:52 2026] 127.0.0.1:52198 Closing
[Tue Apr 21 12:51:52 2026] 127.0.0.1:52208 Accepted
[Tue Apr 21 12:51:53 2026] 127.0.0.1:52208 Closing
[Tue Apr 21 12:52:37 2026] PHP 8.2.30 Development Server (http://127.0.0.1:45395) started
[Tue Apr 21 12:52:37 2026] 127.0.0.1:45928 Accepted
[Tue Apr 21 12:52:39 2026] 127.0.0.1:45928 Closing
[Tue Apr 21 12:52:39 2026] 127.0.0.1:45940 Accepted
[Tue Apr 21 12:52:40 2026] 127.0.0.1:45940 Closing
[Tue Apr 21 12:52:40 2026] 127.0.0.1:45956 Accepted
[Tue Apr 21 12:52:41 2026] 127.0.0.1:45956 Closing
[Tue Apr 21 12:52:41 2026] 127.0.0.1:45968 Accepted
[Tue Apr 21 12:52:42 2026] 127.0.0.1:45968 Closing
[Tue Apr 21 12:52:42 2026] 127.0.0.1:45980 Accepted
[Tue Apr 21 12:52:42 2026] 127.0.0.1:45980 Closing
[Tue Apr 21 12:52:43 2026] 127.0.0.1:45996 Accepted
[Tue Apr 21 12:52:44 2026] 127.0.0.1:45996 Closing
[Tue Apr 21 12:52:44 2026] 127.0.0.1:46000 Accepted
[Tue Apr 21 12:52:45 2026] 127.0.0.1:46000 Closing
[Tue Apr 21 12:52:45 2026] 127.0.0.1:37320 Accepted
[Tue Apr 21 12:52:45 2026] 127.0.0.1:37320 Closing
[Tue Apr 21 12:52:45 2026] 127.0.0.1:37334 Accepted
[Tue Apr 21 12:52:46 2026] 127.0.0.1:37334 Closing
[Tue Apr 21 12:52:46 2026] 127.0.0.1:37346 Accepted
[Tue Apr 21 12:52:47 2026] 127.0.0.1:37346 Closing
[Tue Apr 21 12:52:47 2026] 127.0.0.1:37360 Accepted
[Tue Apr 21 12:52:47 2026] 127.0.0.1:37360 Closing
[Tue Apr 21 12:52:47 2026] 127.0.0.1:37376 Accepted
[Tue Apr 21 12:52:48 2026] 127.0.0.1:37376 Closing
[Tue Apr 21 12:52:48 2026] 127.0.0.1:37378 Accepted
[Tue Apr 21 12:52:48 2026] 127.0.0.1:37378 Closing
[Tue Apr 21 12:52:48 2026] 127.0.0.1:37390 Accepted
[Tue Apr 21 12:52:49 2026] 127.0.0.1:37390 Closing
[Tue Apr 21 12:52:49 2026] 127.0.0.1:37406 Accepted
[Tue Apr 21 12:52:50 2026] 127.0.0.1:37406 Closing
[Tue Apr 21 12:52:50 2026] 127.0.0.1:37416 Accepted
[Tue Apr 21 12:52:50 2026] 127.0.0.1:37416 Closing
[Tue Apr 21 12:52:51 2026] 127.0.0.1:37428 Accepted
[Tue Apr 21 12:52:51 2026] 127.0.0.1:37428 Closing
[Tue Apr 21 12:52:51 2026] 127.0.0.1:37440 Accepted
[Tue Apr 21 12:52:52 2026] 127.0.0.1:37440 Closing
[Tue Apr 21 12:52:52 2026] 127.0.0.1:37450 Accepted
[Tue Apr 21 12:52:53 2026] 127.0.0.1:37450 Closing
[Tue Apr 21 12:52:53 2026] 127.0.0.1:37452 Accepted
[Tue Apr 21 12:52:54 2026] 127.0.0.1:37452 Closing
[Tue Apr 21 12:52:54 2026] 127.0.0.1:37458 Accepted
[Tue Apr 21 12:52:54 2026] 127.0.0.1:37458 Closing
[Tue Apr 21 12:52:54 2026] 127.0.0.1:37466 Accepted
[Tue Apr 21 12:52:55 2026] 127.0.0.1:37466 Closing
[Tue Apr 21 12:52:55 2026] 127.0.0.1:44262 Accepted
[Tue Apr 21 12:52:55 2026] 127.0.0.1:44262 Closing
[Tue Apr 21 12:52:55 2026] 127.0.0.1:44268 Accepted
[Tue Apr 21 12:52:56 2026] 127.0.0.1:44268 Closing
[Tue Apr 21 12:52:56 2026] 127.0.0.1:44284 Accepted
[Tue Apr 21 12:52:56 2026] 127.0.0.1:44284 Closing
[Tue Apr 21 12:52:56 2026] 127.0.0.1:44290 Accepted
[Tue Apr 21 12:52:57 2026] 127.0.0.1:44290 Closing
[Tue Apr 21 12:52:57 2026] 127.0.0.1:44296 Accepted
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44296 Closing
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44312 Accepted
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44312 Closing
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44328 Accepted
[Tue Apr 21 12:52:59 2026] 127.0.0.1:44328 Closing
[Tue Apr 21 12:52:59 2026] 127.0.0.1:44332 Accepted
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44332 Closing
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44334 Accepted
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44334 Closing
[Tue Apr 21 12:52:58 2026] 127.0.0.1:44346 Accepted
[Tue Apr 21 12:52:59 2026] 127.0.0.1:44346 Closing
[Tue Apr 21 12:52:59 2026] 127.0.0.1:44360 Accepted
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44360 Closing
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44372 Accepted
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44372 Closing
[Tue Apr 21 12:53:00 2026] 127.0.0.1:44384 Accepted
[Tue Apr 21 12:53:01 2026] 127.0.0.1:44384 Closing
[Tue Apr 21 12:53:01 2026] 127.0.0.1:44400 Accepted
[Tue Apr 21 12:53:02 2026] 127.0.0.1:44400 Closing
[Tue Apr 21 12:53:02 2026] 127.0.0.1:60944 Accepted
[Tue Apr 21 12:53:03 2026] 127.0.0.1:60944 Closing
[Tue Apr 21 12:53:03 2026] 127.0.0.1:60958 Accepted
[Tue Apr 21 12:53:04 2026] 127.0.0.1:60958 Closing
[Tue Apr 21 12:53:04 2026] 127.0.0.1:60974 Accepted
[Tue Apr 21 12:53:04 2026] 127.0.0.1:60974 Closing
[Tue Apr 21 12:53:05 2026] 127.0.0.1:60982 Accepted
[Tue Apr 21 12:53:05 2026] 127.0.0.1:60982 Closing
[Tue Apr 21 12:53:06 2026] 127.0.0.1:60984 Accepted
[Tue Apr 21 12:53:06 2026] 127.0.0.1:60984 Closing
[Tue Apr 21 12:53:07 2026] 127.0.0.1:60986 Accepted
[Tue Apr 21 12:53:07 2026] 127.0.0.1:60986 Closing
[Tue Apr 21 12:53:08 2026] 127.0.0.1:60990 Accepted
[Tue Apr 21 12:53:08 2026] 127.0.0.1:60990 Closing
[Tue Apr 21 12:53:09 2026] 127.0.0.1:32768 Accepted
[Tue Apr 21 12:53:11 2026] 127.0.0.1:32768 Closing
[Tue Apr 21 12:53:12 2026] 127.0.0.1:32772 Accepted
[Tue Apr 21 12:53:12 2026] 127.0.0.1:32772 Closing
[Tue Apr 21 12:53:12 2026] 127.0.0.1:60592 Accepted
[Tue Apr 21 12:53:13 2026] 127.0.0.1:60592 Closing
[Tue Apr 21 12:53:13 2026] 127.0.0.1:60600 Accepted
[Tue Apr 21 12:53:14 2026] 127.0.0.1:60600 Closing
[Tue Apr 21 12:53:14 2026] 127.0.0.1:60610 Accepted
[Tue Apr 21 12:53:15 2026] 127.0.0.1:60610 Closing
[Tue Apr 21 12:53:15 2026] 127.0.0.1:60626 Accepted
[Tue Apr 21 12:53:16 2026] 127.0.0.1:60626 Closing
[Tue Apr 21 12:53:16 2026] 127.0.0.1:60628 Accepted
[Tue Apr 21 12:53:17 2026] 127.0.0.1:60628 Closing
[Tue Apr 21 12:53:17 2026] 127.0.0.1:60634 Accepted
[Tue Apr 21 12:53:17 2026] 127.0.0.1:60634 Closing
[Tue Apr 21 12:53:18 2026] 127.0.0.1:60638 Accepted
[Tue Apr 21 12:53:18 2026] 127.0.0.1:60638 Closing
[Tue Apr 21 12:53:19 2026] 127.0.0.1:60640 Accepted
[Tue Apr 21 12:53:19 2026] 127.0.0.1:60640 Closing
[Tue Apr 21 12:53:19 2026] 127.0.0.1:60642 Accepted
[Tue Apr 21 12:53:20 2026] 127.0.0.1:60642 Closing
[Tue Apr 21 12:53:20 2026] 127.0.0.1:60658 Accepted
[Tue Apr 21 12:53:21 2026] 127.0.0.1:60658 Closing
[Tue Apr 21 12:53:21 2026] 127.0.0.1:60674 Accepted
[Tue Apr 21 12:53:24 2026] 127.0.0.1:60674 Closing
[Tue Apr 21 12:53:24 2026] 127.0.0.1:57922 Accepted
[Tue Apr 21 12:53:25 2026] 127.0.0.1:57922 Closing
[Tue Apr 21 12:53:25 2026] 127.0.0.1:57928 Accepted
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57928 Closing
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57942 Accepted
[Tue Apr 21 12:53:26 2026] 127.0.0.1:57942 Closing
[Tue Apr 21 12:53:26 2026] 127.0.0.1:57946 Accepted
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57946 Closing
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57954 Accepted
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57954 Closing
[Tue Apr 21 12:53:27 2026] 127.0.0.1:57960 Accepted
[Tue Apr 21 12:53:28 2026] 127.0.0.1:57960 Closing
[Tue Apr 21 12:53:28 2026] 127.0.0.1:57972 Accepted
[Tue Apr 21 12:53:29 2026] 127.0.0.1:57972 Closing
[Tue Apr 21 12:53:29 2026] 127.0.0.1:57986 Accepted
[Tue Apr 21 12:53:30 2026] 127.0.0.1:57986 Closing
[Tue Apr 21 12:53:30 2026] 127.0.0.1:41580 Accepted
[Tue Apr 21 12:53:31 2026] 127.0.0.1:41580 Closing
[Tue Apr 21 12:53:31 2026] 127.0.0.1:41588 Accepted
[Tue Apr 21 12:53:31 2026] 127.0.0.1:41588 Closing
[Tue Apr 21 12:53:31 2026] 127.0.0.1:41602 Accepted
[Tue Apr 21 12:53:33 2026] 127.0.0.1:41602 Closing
[Tue Apr 21 12:53:33 2026] 127.0.0.1:41608 Accepted
[Tue Apr 21 12:53:34 2026] 127.0.0.1:41608 Closing
[Tue Apr 21 12:53:34 2026] 127.0.0.1:41624 Accepted
[Tue Apr 21 12:53:34 2026] 127.0.0.1:41624 Closing
[Tue Apr 21 12:53:34 2026] 127.0.0.1:41634 Accepted
[Tue Apr 21 12:53:35 2026] 127.0.0.1:41634 Closing
[Tue Apr 21 12:53:35 2026] 127.0.0.1:41638 Accepted
[Tue Apr 21 12:53:36 2026] 127.0.0.1:41638 Closing
[Tue Apr 21 12:53:36 2026] 127.0.0.1:41644 Accepted
[Tue Apr 21 12:53:36 2026] 127.0.0.1:41644 Closing
[Tue Apr 21 12:53:36 2026] 127.0.0.1:41650 Accepted
[Tue Apr 21 12:53:37 2026] 127.0.0.1:41650 Closing
[Tue Apr 21 12:53:37 2026] 127.0.0.1:41652 Accepted
[Tue Apr 21 12:53:37 2026] 127.0.0.1:41652 Closing
[Tue Apr 21 12:53:38 2026] 127.0.0.1:41654 Accepted
[Tue Apr 21 12:53:38 2026] 127.0.0.1:41654 Closing
[Tue Apr 21 12:53:38 2026] 127.0.0.1:41660 Accepted
[Tue Apr 21 12:53:38 2026] 127.0.0.1:41660 Closing
[Tue Apr 21 12:53:38 2026] 127.0.0.1:41664 Accepted
[Tue Apr 21 12:53:39 2026] 127.0.0.1:41664 Closing
[Tue Apr 21 12:53:39 2026] 127.0.0.1:36412 Accepted
[Tue Apr 21 12:53:40 2026] 127.0.0.1:36412 Closing
[Tue Apr 21 12:53:40 2026] 127.0.0.1:36420 Accepted
[Tue Apr 21 12:53:41 2026] 127.0.0.1:36420 Closing
[Tue Apr 21 12:53:41 2026] 127.0.0.1:36426 Accepted
[Tue Apr 21 12:53:42 2026] 127.0.0.1:36426 Closing
[Tue Apr 21 12:53:42 2026] 127.0.0.1:36442 Accepted
[Tue Apr 21 12:53:44 2026] 127.0.0.1:36442 Closing
[Tue Apr 21 12:53:44 2026] 127.0.0.1:36450 Accepted
[Tue Apr 21 12:53:47 2026] 127.0.0.1:36450 Closing
[Tue Apr 21 12:53:49 2026] 127.0.0.1:47160 Accepted
[Tue Apr 21 12:53:52 2026] 127.0.0.1:47160 Closing
[Tue Apr 21 12:53:52 2026] 127.0.0.1:47174 Accepted
[Tue Apr 21 12:53:54 2026] 127.0.0.1:47174 Closing
[Tue Apr 21 12:53:54 2026] 127.0.0.1:47178 Accepted
[Tue Apr 21 12:53:53 2026] 127.0.0.1:47178 Closing
[Tue Apr 21 12:53:53 2026] 127.0.0.1:47194 Accepted
[Tue Apr 21 12:53:55 2026] 127.0.0.1:47194 Closing
[Tue Apr 21 12:53:55 2026] 127.0.0.1:47204 Accepted
[Tue Apr 21 12:53:57 2026] 127.0.0.1:47204 Closing
[Tue Apr 21 12:53:57 2026] 127.0.0.1:46990 Accepted
[Tue Apr 21 12:53:59 2026] 127.0.0.1:46990 Closing
[Tue Apr 21 12:53:59 2026] 127.0.0.1:46994 Accepted
[Tue Apr 21 12:54:01 2026] 127.0.0.1:46994 Closing
[Tue Apr 21 12:54:01 2026] 127.0.0.1:46996 Accepted
[Tue Apr 21 12:54:03 2026] 127.0.0.1:46996 Closing
[Tue Apr 21 12:54:03 2026] 127.0.0.1:47008 Accepted
[Tue Apr 21 12:54:04 2026] 127.0.0.1:47008 Closing
[Tue Apr 21 12:54:04 2026] 127.0.0.1:47012 Accepted
[Tue Apr 21 12:54:05 2026] 127.0.0.1:47012 Closing
[Tue Apr 21 12:54:05 2026] 127.0.0.1:47026 Accepted
[Tue Apr 21 12:54:06 2026] 127.0.0.1:47026 Closing
+1 -1
View File
@@ -2,7 +2,7 @@
$runtimeEnvironment = getenv();
if (is_array($runtimeEnvironment) && $runtimeEnvironment !== []) {
$_ENV = array_merge($runtimeEnvironment, $_ENV ?? []);
$_ENV = array_merge($_ENV ?? [], $runtimeEnvironment);
}
/**
@@ -2,7 +2,6 @@
namespace customers;
use classes\response;
use objects\users_o;
class economic_customer_mo
@@ -33,11 +32,14 @@ class economic_customer_mo
}
public function parseCustomer($customer): static
public function parseCustomer(mixed $customer): static
{
global /** @var response $response */
$response;
$this->customer_number = $customer->customerNumber;
$customer_number = $this->extractCustomerNumber($customer);
if ($customer_number === null) {
return $this;
}
$this->customer_number = $customer_number;
$this->name = ($customer->name ?? null);
$this->address = ($customer->address ?? null);
$this->city = ($customer->city ?? null);
@@ -48,6 +50,33 @@ class economic_customer_mo
$this->currency = ($customer->currency ?? null);
$this->country = ($customer->country ?? null);
$this->barred = (isset($customer->barred) ? (bool)$customer->barred : false);
$this->cacheCustomerForImportedUser();
// Add the customer to the debug log (IF DEBUG IS ENABLED)
//$response->add_debug_list('economic_customer', $this->customer_number, $customer);
return $this;
}
protected function extractCustomerNumber(mixed $customer): ?int
{
if (!is_object($customer)) {
return null;
}
$customer_number = $customer->customerNumber ?? $customer->customer_number ?? null;
if (!is_int($customer_number) && !is_float($customer_number) && !is_string($customer_number)) {
return null;
}
$customer_number = (int)$customer_number;
if ($customer_number <= 0) {
return null;
}
return $customer_number;
}
protected function cacheCustomerForImportedUser(): void
{
$users_o = new users_o();
$user = $users_o->getUserByCustomerNumber($this->customer_number);
@@ -55,9 +84,6 @@ class economic_customer_mo
if ($user->exists()) {
$user->cache('economic_customer', $this);
}
// Add the customer to the debug log (IF DEBUG IS ENABLED)
//$response->add_debug_list('economic_customer', $this->customer_number, $customer);
return $this;
}
public function asArray(): array
@@ -80,4 +106,4 @@ class economic_customer_mo
'barred' => $this->barred,
];
}
}
}
@@ -546,9 +546,6 @@ class collected_order_invoices_o extends db
$db;
// Sanitize the input
$customer_number = $db->escape_string($customer_number);
if (!empty($name)) {
$name = $db->escape_string($name);
}
if (!empty($notes)) {
$notes = $db->escape_string($notes);
}
@@ -564,10 +561,20 @@ class collected_order_invoices_o extends db
}
// Require the customer number to be of a valid customer
self::requireValidCustomer($customer_number);
$resolved_name = is_string($name) ? trim($name) : '';
if ($resolved_name === '') {
$customer = (new users_o())->getUserByCustomerNumber((int)$customer_number);
$customer->requireSelected();
$resolved_name = trim((string)$customer->display_name->value());
}
if ($resolved_name === '') {
$resolved_name = 'Invoice collection ' . (string)$customer_number;
}
$resolved_name = $db->escape_string($resolved_name);
// Add the object
$tmp_id = self::add_object([
'customer_number' => (int)$customer_number,
'name' => $name,
'name' => $resolved_name,
'notes' => $notes,
...(!empty($closed_at) ? ['closed_at' => (string)$closed_at] : []),
]);
+28 -4
View File
@@ -477,16 +477,27 @@ class users_o extends db
// Create a temporary user object
$tmp_user = new users_o();
$tmp_user->getUserByCustomerNumber($customer_number);
$fallbackName = null;
if ($tmp_user->exists()) {
$displayName = $tmp_user->display_name->value();
if (is_string($displayName) && trim($displayName) !== '') {
$fallbackName = $displayName;
}
}
// Get the customer name (Check cache first)
$cached = $tmp_user->getCached('economic_customer');
if (!$cached) {
$tmp_user->getCustomerEcocomicData($customer_number);
try {
$tmp_user->getCustomerEcocomicData($customer_number);
} catch (Exception) {
return $fallbackName;
}
$cached = $tmp_user->getCached('economic_customer');
}
if ($cached) {
if ($cached && isset($cached->name) && is_string($cached->name) && trim($cached->name) !== '') {
return $cached->name;
}
return null;
return $fallbackName;
}
public function getCustomerEcocomicData(int $customer_number = null): users_o
@@ -502,7 +513,20 @@ class users_o extends db
return $this;
}
$this->economic_customer = (new economic_customer_mo())->getCustomerByCustomerNumber($customer_number);
$cachedCustomer = $this->getCached('economic_customer');
if (is_object($cachedCustomer)) {
$cachedCustomerNumber = (int)($cachedCustomer->customerNumber ?? $cachedCustomer->customer_number ?? 0);
if ($cachedCustomerNumber === $customer_number) {
$this->economic_customer = (new economic_customer_mo())->parseCustomer($cachedCustomer);
return $this;
}
}
try {
$this->economic_customer = (new economic_customer_mo())->getCustomerByCustomerNumber($customer_number);
} catch (Exception) {
$this->economic_customer = new economic_customer_mo();
}
return $this;
}
@@ -414,6 +414,17 @@ CREATE TABLE IF NOT EXISTS `stripe_module_orders` (
`updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL,
'stripe_module_customers' => <<<'SQL'
CREATE TABLE IF NOT EXISTS `stripe_module_customers` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255) NOT NULL,
`customer_id` VARCHAR(255) NOT NULL,
`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_stripe_module_customers_email` (`email`),
KEY `idx_stripe_module_customers_customer_id` (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
SQL,
'stripe_payment_intents' => <<<'SQL'
CREATE TABLE IF NOT EXISTS `stripe_payment_intents` (
@@ -33,6 +33,7 @@ final class ApiServer
escapeshellarg((string)(PHP_BINARY ?: 'php')),
escapeshellarg($this->host . ':' . $this->port),
);
$environment = $this->inheritEnvironment();
$descriptorSpec = [
0 => ['pipe', 'r'],
@@ -40,7 +41,7 @@ final class ApiServer
2 => ['file', $this->stderrLog, 'a'],
];
$this->process = proc_open($command, $descriptorSpec, $pipes, $this->workingDirectory);
$this->process = proc_open($command, $descriptorSpec, $pipes, $this->workingDirectory, $environment);
if (!is_resource($this->process)) {
throw new RuntimeException('Unable to start the PHP test server.');
}
@@ -88,4 +89,62 @@ final class ApiServer
return $message;
}
public static function findAvailablePort(string $host): int
{
$socket = @stream_socket_server('tcp://' . $host . ':0', $errorCode, $errorMessage);
if ($socket === false) {
throw new RuntimeException(
sprintf('Unable to reserve an API test port on %s: %s (%d)', $host, $errorMessage, $errorCode)
);
}
$address = stream_socket_get_name($socket, false);
fclose($socket);
if (!is_string($address) || $address === '') {
throw new RuntimeException('Unable to determine the reserved API test port.');
}
$lastSeparator = strrpos($address, ':');
if ($lastSeparator === false) {
throw new RuntimeException('Unable to parse the reserved API test port.');
}
$port = (int)substr($address, $lastSeparator + 1);
if ($port <= 0) {
throw new RuntimeException('Unable to parse a valid API test port.');
}
return $port;
}
/**
* @return array<string, string>
*/
private function inheritEnvironment(): array
{
$environment = [];
$sources = [getenv(), $_ENV ?? [], $_SERVER ?? []];
foreach ($sources as $source) {
if (!is_array($source)) {
continue;
}
foreach ($source as $key => $value) {
if (!is_string($key) || $key === '') {
continue;
}
if (!is_scalar($value) && $value !== null) {
continue;
}
$environment[$key] = $value === null ? '' : (string)$value;
}
}
return $environment;
}
}
@@ -25,6 +25,7 @@ final class ApiTestRuntime
private bool $shutdownRegistered = false;
private string $baseUrl = self::DEFAULT_BASE_URL;
private bool $usesExternalBaseUrl = false;
private ?int $internalServerPort = null;
public static function instance(): self
{
@@ -58,7 +59,6 @@ final class ApiTestRuntime
$this->bootstrapSchemaIfRequested();
$this->cleanup = new ApiCleanup();
$this->fixtures = new ApiFixtures($this->db(), $this->redis(), $this->cleanup);
$this->ensureServerIsRunning();
}
public function endTest(): void
@@ -137,6 +137,7 @@ final class ApiTestRuntime
$this->bootstrapped = false;
$this->schemaBootstrapped = false;
$this->internalServerPort = null;
}
private function bootstrapEnvironment(): void
@@ -212,7 +213,9 @@ final class ApiTestRuntime
$url = parse_url($this->baseUrl);
$host = (string)($url['host'] ?? '127.0.0.1');
$port = (int)($url['port'] ?? 18080);
$port = $this->internalServerPort ?? ApiServer::findAvailablePort($host);
$this->internalServerPort = $port;
$this->baseUrl = sprintf('http://%s:%d', $host, $port);
$this->server = new ApiServer(app_path(), $host, $port);
$this->server->start();
@@ -0,0 +1,47 @@
<?php
app_require('modules/economic/customers/economic_customer_mo.php');
use customers\economic_customer_mo;
if (!class_exists('EconomicCustomerModelParsingProbe')) {
class EconomicCustomerModelParsingProbe extends economic_customer_mo
{
protected function cacheCustomerForImportedUser(): void
{
}
}
}
it('parses cached economic customer payloads that use snake_case customer_number', function (): void {
$model = (new EconomicCustomerModelParsingProbe())->parseCustomer((object)[
'customer_number' => '42331123',
'name' => 'Truckwash ApS',
'email' => 'jb@truckwash.dk',
'currency' => 'DKK',
'country' => 'DK',
'barred' => true,
]);
expect($model->asArray())->toBe([
'customerNumber' => 42331123,
'name' => 'Truckwash ApS',
'address' => null,
'city' => null,
'zip' => null,
'corporateIdentificationNumber' => null,
'email' => 'jb@truckwash.dk',
'mobilePhone' => null,
'currency' => 'DKK',
'country' => 'DK',
'barred' => true,
]);
});
it('leaves the economic customer model empty when no valid customer number is present', function (): void {
$model = (new EconomicCustomerModelParsingProbe())->parseCustomer((object)[
'name' => 'Missing Number',
]);
expect($model->asArray())->toBe([]);
});
+101 -1
View File
@@ -24,6 +24,10 @@ trait minio_t
*/
public function listFiles(): array
{
if ($this->shouldUseLocalTestStorage()) {
return $this->listLocalTestObjects();
}
$objects = self::getS3Client()->listObjects([
'Bucket' => self::getBucket()
]);
@@ -123,6 +127,10 @@ trait minio_t
*/
public function createObject(string $key, string $body): bool
{
if ($this->shouldUseLocalTestStorage()) {
return file_put_contents($this->getLocalTestObjectPath($key), $body) !== false;
}
$result = self::getS3Client()->putObject([
'Bucket' => self::getBucket(),
'Key' => $key,
@@ -138,6 +146,10 @@ trait minio_t
*/
public function getObjectUrl(string $key): string
{
if ($this->shouldUseLocalTestStorage()) {
return $this->getLocalTestObjectPath($key);
}
return self::getS3Client()->getObjectUrl(self::getBucket(), $key);
}
@@ -153,6 +165,10 @@ trait minio_t
*/
public function getPresignedUrl(string $key, ?int $expires = null, bool $isUpload = false): string
{
if ($this->shouldUseLocalTestStorage()) {
return $this->getLocalTestObjectPath($key);
}
$commandAction = $isUpload ? 'PutObject' : 'GetObject';
$command = self::getS3Client()->getCommand($commandAction, [
'Bucket' => self::getBucket(),
@@ -171,6 +187,10 @@ trait minio_t
*/
public function uploadFile(string $key, string $file): bool
{
if ($this->shouldUseLocalTestStorage()) {
return copy($file, $this->getLocalTestObjectPath($key));
}
$result = self::getS3Client()->putObject([
'Bucket' => self::getBucket(),
'Key' => $key,
@@ -186,6 +206,10 @@ trait minio_t
*/
public function doesObjectExist(string $key): bool
{
if ($this->shouldUseLocalTestStorage()) {
return file_exists($this->getLocalTestObjectPath($key));
}
return self::getS3Client()->doesObjectExist(self::getBucket(), $key);
}
@@ -305,4 +329,80 @@ trait minio_t
// If no match, return a default extension (e.g., 'jpg')
return 'jpg';
}
}
private function shouldUseLocalTestStorage(): bool
{
if (getenv('RUN_API_TESTS') !== '1') {
return false;
}
return trim((string)$this->getEndpoint()) === ''
|| trim((string)$this->getAccessKey()) === ''
|| trim((string)$this->getSecretKey()) === '';
}
/**
* @return array<int, array{Key:string}>
*/
private function listLocalTestObjects(): array
{
$directory = $this->getLocalTestStorageDirectory();
$objects = [];
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $fileInfo) {
if (!$fileInfo->isFile()) {
continue;
}
$pathname = $fileInfo->getPathname();
$relativePath = substr($pathname, strlen($directory) + 1);
$objects[] = [
'Key' => str_replace('\\', '/', $relativePath),
];
}
return $objects;
}
private function getLocalTestStorageDirectory(): string
{
$bucket = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $this->getBucket()) ?: 'default';
$directory = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR)
. DIRECTORY_SEPARATOR
. 'truckwash-test-object-store'
. DIRECTORY_SEPARATOR
. $bucket;
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
return $directory;
}
private function getLocalTestObjectPath(string $key): string
{
$normalizedKey = str_replace('\\', '/', $key);
$normalizedKey = preg_replace('#(^|/)\\.\\.(?=/|$)#', '', $normalizedKey) ?? $normalizedKey;
$normalizedKey = ltrim($normalizedKey, '/');
if ($normalizedKey === '') {
$normalizedKey = 'object';
}
$path = $this->getLocalTestStorageDirectory()
. DIRECTORY_SEPARATOR
. str_replace('/', DIRECTORY_SEPARATOR, $normalizedKey);
$directory = dirname($path);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
return $path;
}
}
@@ -71,7 +71,7 @@ trait module_config_variable
*/
static function isVariableSet(string $module, string $variable): bool
{
global $db;
$db = self::getModuleConfigDatabase();
$module = $db->escape_string($module);
$variable = $db->escape_string($variable);
$sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'";
@@ -87,7 +87,7 @@ trait module_config_variable
*/
function insertVariableValue(string $module, string $variable, mixed $value): void
{
global $db;
$db = self::getModuleConfigDatabase();
$module = $db->escape_string($module);
$variable = $db->escape_string($variable);
$value = $db->escape_string((string)$value);
@@ -126,7 +126,7 @@ trait module_config_variable
*/
function getVariableValue(): mixed
{
global $db;
$db = self::getModuleConfigDatabase();
$module = $db->escape_string($this->module_name);
$variable = $db->escape_string($this->config_variable);
$sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'";
@@ -242,7 +242,7 @@ trait module_config_variable
*/
static function updateVariableValue(string $module, string $variable, mixed $value): void
{
global $db;
$db = self::getModuleConfigDatabase();
$module = $db->escape_string($module);
$variable = $db->escape_string($variable);
$value = $db->escape_string((string)$value);
@@ -251,6 +251,57 @@ trait module_config_variable
system_search_cache::markDirtyTable('module_config');
}
private static function getModuleConfigDatabase(): \classes\db
{
global $db, $CONFIG_DB;
if ($db instanceof \classes\db) {
return $db;
}
if (!is_array($CONFIG_DB) || $CONFIG_DB === []) {
$CONFIG_DB = self::readDatabaseConfigFromEnvironment();
}
if (!is_array($CONFIG_DB) || $CONFIG_DB === []) {
throw new Exception('Database configuration is not available');
}
$db = new \classes\db($CONFIG_DB);
$db->connect();
return $db;
}
private static function readDatabaseConfigFromEnvironment(): array
{
$readEnv = static function (string $key, string $default = ''): string {
$value = $_ENV[$key] ?? getenv($key);
if ($value === false || $value === null) {
return $default;
}
return trim((string)$value);
};
$host = $readEnv('CONFIG_DB_HOST');
$user = $readEnv('CONFIG_DB_USER');
$database = $readEnv('CONFIG_DB_DATABASE');
if ($host === '' || $user === '' || $database === '') {
return [];
}
return [
'host' => $host,
'user' => $user,
'password' => $readEnv('CONFIG_DB_PASSWORD'),
'database' => $database,
'port' => (int)($readEnv('CONFIG_DB_PORT', '3306') ?: '3306'),
'ssl_mode' => $readEnv('CONFIG_DB_SSL_MODE', 'DISABLED') ?: 'DISABLED',
];
}
/**
* Get the config variable as a JSON string
* @return string