dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
dbxInstallationService_test.php
Go to the documentation of this file.
1<?php
2
3declare(strict_types=1);
4
5$root = dirname(__DIR__, 4);
6chdir($root);
7$_SERVER['REQUEST_URI'] = '/dbxapp/';
8$_SERVER['HTTP_HOST'] = 'localhost';
9$_SERVER['HTTPS'] = 'on';
10$_SERVER['SCRIPT_NAME'] = '/dbxapp/index.php';
11
12if (!defined('dbxSystem')) {
13 define('dbxSystem', 'dbxWebApp');
14}
15if (!defined('dbxRunAsAdmin')) {
16 define('dbxRunAsAdmin', 1);
17}
18
19require_once $root . '/dbx/vendor/autoload.php';
20require_once $root . '/dbx/include/dbxKernel.php';
21require_once dirname(__DIR__) . '/include/dbxInstallationService.class.php';
22
24
26{
27 public array $rows = array();
28 public array $inserted = array();
29 public array $updated = array();
30 private int $nextId = 1;
31
32 public function get_dd_server_binding_info(string $dd): array
33 {
34 return array(
35 'valid' => true,
36 'resolved_server' => str_ends_with($dd, 'dbxUser')
37 ? 'mysqlUsers'
38 : 'dbxTest.db3',
39 );
40 }
41
42 public function select1(
43 string $dd,
44 array $where,
45 array $columns,
46 int $verifyAccess
47 ): array {
48 foreach ($this->rows[$dd] ?? array() as $row) {
49 $match = true;
50 foreach ($where as $name => $value) {
51 if (($row[$name] ?? null) !== $value) {
52 $match = false;
53 break;
54 }
55 }
56 if ($match) {
57 return $row;
58 }
59 }
60 return array();
61 }
62
63 public function insert(
64 string $dd,
65 array $values,
66 int $verifyAccess,
67 int $verifyFields,
68 int $verifyValues,
69 int $trace
70 ): int {
71 $values['id'] = $this->nextId++;
72 $this->rows[$dd][] = $values;
73 $this->inserted[] = array('dd' => $dd, 'values' => $values);
74 return (int)$values['id'];
75 }
76
77 public function update(
78 string $dd,
79 array $values,
80 int $id,
81 int $verifyAccess,
82 int $verifyFields,
83 int $verifyValues,
84 int $trace
85 ): int {
86 foreach ($this->rows[$dd] ?? array() as $index => $row) {
87 if ((int)($row['id'] ?? 0) !== $id) {
88 continue;
89 }
90 $this->rows[$dd][$index] = array_replace($row, $values);
91 $this->updated[] = array(
92 'dd' => $dd,
93 'id' => $id,
94 'values' => $values,
95 );
96 return 1;
97 }
98 return 0;
99 }
100}
101
103{
104 public array $calls = array();
105 public array $transfers = array();
106 public bool $allTablesExist = false;
107
108 public function sync_dd_to_db(string $module, string $dd, string $mode): array
109 {
110 $this->calls[] = array($module, $dd, $mode);
111 if ($mode === 'reset') {
112 return array('status' => 'reset');
113 }
114 return array('status' => 'finished', 'message' => 'ok');
115 }
116
117 public function get_table_exist(string $server, string $table): bool
118 {
119 return $this->allTablesExist || $table === 'dbx_user';
120 }
121
122 public function transfer_table(
123 string $sourceServer,
124 string $sourceTable,
125 string $targetServer,
126 string $targetTable,
127 string $mode,
128 int $createTarget,
129 int $truncateTarget
130 ): array {
131 $this->transfers[] = array(
132 $sourceServer,
133 $sourceTable,
134 $targetServer,
135 $targetTable,
136 $mode,
137 $createTarget,
138 $truncateTarget,
139 );
140 return array(
141 'status' => $mode === 'reset' ? 'reset' : 'finished',
142 'message' => 'ok',
143 );
144 }
145}
146
147function installation_assert(bool $condition, string $message): void
148{
149 if (!$condition) {
150 throw new RuntimeException($message);
151 }
152}
153
156$service = new dbxInstallationService($db, $dd);
157
158$catalog = $service->discoverDDs(array('dbx'));
159installation_assert(count($catalog) >= 10, 'Core-DD-Katalog ist unvollstaendig.');
161 in_array('dbx|dbxUser', array_column($catalog, 'dd'), true),
162 'dbxUser fehlt im Installationskatalog.'
163);
164$userDefinition = array_values(array_filter(
165 $catalog,
166 static fn(array $record): bool => $record['dd'] === 'dbx|dbxUser'
167))[0] ?? array();
169 ($userDefinition['declared_server'] ?? '') === 'dbx|dbxUser.db3'
170 && ($userDefinition['table'] ?? '') === 'dbx_user',
171 'Deklarierter DD-Quellspeicher wird nicht erkannt.'
172);
173
174$schema = $service->provisionSchema(array('dbx'));
175installation_assert($schema['ok'] === true, 'DD-Provisionierung meldet einen Fehler.');
177 $schema['finished'] === $schema['total'],
178 'Nicht alle DDs wurden provisioniert.'
179);
180
181$dd->allTablesExist = true;
182$verification = $service->verifyBundledSchema(array('dbx'));
184 $verification['ok'] === true
185 && $verification['verified'] === $verification['total']
186 && count($dd->calls) === $schema['total'] * 2,
187 'Lesende DB3-Prüfung darf keine zusätzliche Schema-Synchronisierung auslösen.'
188);
189$dd->allTablesExist = false;
190
191$transfer = $service->transferDeclaredDataToServer('dbxApp', array('dbx'));
193 $transfer['ok'] === true
194 && $transfer['transferred'] === 1
195 && count($dd->transfers) === 2
196 && $dd->transfers[0][4] === 'reset'
197 && $dd->transfers[1][4] === 'step',
198 'Optionale DD-Datenübertragung ist nicht deterministisch.'
199);
200
201$groups = $service->seedCoreGroups();
203 $groups['created'] === array('guest', 'member', 'admin'),
204 'Core-Gruppen wurden nicht deterministisch angelegt.'
205);
206$groupsAgain = $service->seedCoreGroups();
208 $groupsAgain['created'] === array()
209 && $groupsAgain['existing'] === array('guest', 'member', 'admin'),
210 'Core-Seed ist nicht idempotent.'
211);
212
213$admin = $service->createAdmin(
214 'Test-Passwort-2026!',
215 'admin@example.test',
216 'de'
217);
218installation_assert($admin['created'] === true, 'Admin wurde nicht angelegt.');
219$adminRows = array_values(array_filter(
220 $db->inserted,
221 static fn(array $entry): bool => $entry['dd'] === 'dbx|dbxUser'
222));
223installation_assert(count($adminRows) === 1, 'Admin wurde mehrfach geschrieben.');
225 password_verify(
226 'Test-Passwort-2026!',
227 (string)$adminRows[0]['values']['pass']
228 ),
229 'Admin-Passwort wurde nicht mit password_hash gespeichert.'
230);
231
232$adminAgain = $service->createAdmin(
233 'Anderes-Passwort-2026!',
234 'other@example.test',
235 'en'
236);
238 $adminAgain['created'] === false
239 && count(array_filter(
240 $db->inserted,
241 static fn(array $entry): bool => $entry['dd'] === 'dbx|dbxUser'
242 )) === 1,
243 'Vorhandener Admin wurde bei erneutem Seed veraendert.'
244);
245
248$initialService = new dbxInstallationService($initialDb, $initialDd);
249$missingAdmin = $initialService->ensureInitialAdmin(
250 false,
251 'de',
252 'Admin-2026!'
253);
255 $missingAdmin['exists'] === false
256 && $missingAdmin['created'] === false
257 && $missingAdmin['reset'] === false
258 && count($initialDb->inserted) === 0,
259 'Ohne ausdrückliches Sicherstellen darf ein fehlender Admin nicht geschrieben werden.'
260);
261$initialAdmin = $initialService->ensureInitialAdmin(
262 true,
263 'de',
264 'Admin-2026!'
265);
266$initialRows = array_values(array_filter(
267 $initialDb->inserted,
268 static fn(array $entry): bool => $entry['dd'] === 'dbx|dbxUser'
269));
271 $initialAdmin['exists'] === true
272 && $initialAdmin['created'] === true
273 && $initialAdmin['reset'] === true
274 && $initialAdmin['default_password'] === false
275 && $initialAdmin['password_reset_required'] === false
276 && count($initialRows) === 1
277 && password_verify('Admin-2026!', (string)$initialRows[0]['values']['pass']),
278 'Ein leeres SQL-Ziel benötigt den in der Installation gewählten Administratorzugang.'
279);
280
282$existingDb->rows['dbx|dbxUser'][] = array(
283 'id' => 17,
284 'uname' => 'admin',
285 'pass' => password_hash('Individuell-2026!', PASSWORD_DEFAULT),
286 'email' => 'existing@example.test',
287 'roles' => 'member',
288 'language' => 'de',
289 'status' => 0,
290 'is_confirm' => 0,
291 'settings' => json_encode(array(
292 'dashboard_layout' => 'compact',
293 'password_changed_at' => '2026-01-01T00:00:00+00:00',
294 )),
295);
296$existingService = new dbxInstallationService(
299);
300$resetAdmin = $existingService->ensureInitialAdmin(
301 true,
302 'de',
303 'Neues-Admin-2026!'
304);
305$resetRow = $existingDb->rows['dbx|dbxUser'][0];
306$resetSettings = json_decode((string)$resetRow['settings'], true);
308 $resetAdmin['exists'] === true
309 && $resetAdmin['created'] === false
310 && $resetAdmin['reset'] === true
311 && $resetAdmin['default_password'] === false
312 && $resetAdmin['password_reset_required'] === false
313 && password_verify('Neues-Admin-2026!', (string)$resetRow['pass'])
314 && $resetRow['email'] === 'existing@example.test'
315 && $resetRow['roles'] === 'admin'
316 && (int)$resetRow['status'] === 1
317 && (int)$resetRow['is_confirm'] === 1
318 && ($resetSettings['dashboard_layout'] ?? '') === 'compact'
319 && !empty($resetSettings['password_changed_at'])
320 && !isset($resetSettings['password_reset_required'])
321 && count($existingDb->updated) === 1,
322 'Ein vorhandener Admin muss das in der Installation gewählte persönliche Passwort erhalten.'
323);
324
325echo "OK DD installation catalog, schema provisioning and idempotent seeds\n";
$table['server']
Definition .dd.php:6
insert(string $dd, array $values, int $verifyAccess, int $verifyFields, int $verifyValues, int $trace)
update(string $dd, array $values, int $id, int $verifyAccess, int $verifyFields, int $verifyValues, int $trace)
select1(string $dd, array $where, array $columns, int $verifyAccess)
sync_dd_to_db(string $module, string $dd, string $mode)
get_table_exist(string $server, string $table)
transfer_table(string $sourceServer, string $sourceTable, string $targetServer, string $targetTable, string $mode, int $createTarget, int $truncateTarget)
Reproduzierbare Erstinstallation aus DDs und idempotenten Seeds.
$_SERVER['REQUEST_METHOD']
if(preg_match('/core\.js\? foreach[^"\']*v=(\d+)/', $designTemplate, $assetMatch) !== 1 || !str_contains($shopReference, 'dbxapp-Asset-Version ' . $assetMatch[1])) foreach (array( 'reference\\archive', 'provision_docs_content.php', 'dbxSelfTest') as $needle)(array('Installation'=> $installation, 'Installations-Tutorial'=> $installationTutorial, 'SelfTest'=> $selfTest) as $label=> $html)
$existingDb rows['dbx|dbxUser'][]
installation_assert(bool $condition, string $message)