This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
// Offline contract tests only. Native scheduling is verified by the separate kernel fixture.
|
||||
if (PHP_SAPI !== 'cli' || function_exists('IPS_GetKernelVersion')) {
|
||||
throw new RuntimeException('Standalone PHP CLI only; never load mocks into Symcon.');
|
||||
}
|
||||
|
||||
$GLOBALS['deviceCalls'] = 0;
|
||||
$GLOBALS['testParent'] = 20;
|
||||
function MBUS_UpdateValues(int $id): bool { ++$GLOBALS['deviceCalls']; throw new RuntimeException('Unexpected device I/O'); }
|
||||
function ModBus_RequestRead(int $id): bool { ++$GLOBALS['deviceCalls']; throw new RuntimeException('Unexpected device I/O'); }
|
||||
function IPS_VariableExists(int $id): bool { return $id === 10; }
|
||||
function IPS_GetObject(int $id): array { return ['ParentID' => $GLOBALS['testParent'], 'ObjectIdent' => 'grid']; }
|
||||
function IPS_GetInstanceListByModuleID(string $id): array { return [17004]; }
|
||||
function IPS_GetProperty(int $id, string $key): mixed
|
||||
{
|
||||
return $key === 'AutomatischeSuche' ? false : '[{"Aktiv":true,"InstanzID":44234}]';
|
||||
}
|
||||
|
||||
class IPSModule
|
||||
{
|
||||
public int $InstanceID = 44234;
|
||||
public array $properties = [], $attributes = [], $buffers = [], $timers = [], $calls = [];
|
||||
public function Create(): void {}
|
||||
public function __call(string $method, array $args): mixed
|
||||
{
|
||||
if (str_starts_with($method, 'RegisterProperty')) { $this->properties[$args[0]] ??= $args[1]; return null; }
|
||||
if (str_starts_with($method, 'RegisterAttribute')) { $this->attributes[$args[0]] ??= $args[1]; return null; }
|
||||
if (str_starts_with($method, 'RegisterVariable') || $method === 'EnableAction') return null;
|
||||
if (str_starts_with($method, 'ReadProperty')) return $this->properties[$args[0]];
|
||||
if (str_starts_with($method, 'ReadAttribute')) return $this->attributes[$args[0]];
|
||||
if ($method === 'RegisterTimer') { $this->timers[$args[0]] = ['interval' => $args[1], 'script' => $args[2]]; return null; }
|
||||
if ($method === 'GetBuffer') return $this->buffers[$args[0]] ?? '';
|
||||
throw new RuntimeException('Unexpected mock call: ' . $method);
|
||||
}
|
||||
}
|
||||
|
||||
require __DIR__ . '/../../Batterie/module.php';
|
||||
class TimerContractBattery extends Batterie
|
||||
{
|
||||
public function RequestAction($ident, $value): void { $this->calls[] = [$ident, $value]; }
|
||||
}
|
||||
|
||||
$battery = new TimerContractBattery();
|
||||
$battery->Create();
|
||||
$GLOBALS['testBattery'] = $battery;
|
||||
function ENELIX_BatterieTimerAktion(int $id, string $ident, bool $value): void
|
||||
{
|
||||
if ($id !== 44234) throw new RuntimeException('Wrong timer target');
|
||||
$GLOBALS['testBattery']->BatterieTimerAktion($ident, $value);
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
function check(bool $valid, string $name): void
|
||||
{
|
||||
global $count;
|
||||
if (!$valid) throw new RuntimeException('FAIL ' . $name);
|
||||
++$count;
|
||||
echo 'PASS ' . $name . PHP_EOL;
|
||||
}
|
||||
function rejects(callable $call, string $reason): void
|
||||
{
|
||||
try { $call(); }
|
||||
catch (RuntimeException $error) {
|
||||
check($error->getMessage() === $reason, $reason);
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Expected rejection: ' . $reason);
|
||||
}
|
||||
|
||||
$expected = [
|
||||
'Meldezyklus' => ['Melden', false],
|
||||
'RueckmeldungVerzoegert' => ['Melden', true],
|
||||
'VorgabeTimeout' => ['VorgabeTimeout', 0],
|
||||
'AenderungFreigabe' => ['AenderungFreigabe', 0],
|
||||
'NetzfahrplanV4RueckmeldungAktualisieren' => ['NetzfahrplanV4RueckmeldungAktualisieren', 0],
|
||||
'NetzfahrplanV4TestWatchdog' => ['NetzfahrplanV4TestWatchdog', 0],
|
||||
];
|
||||
check(count($battery->timers) === count($expected), 'same six native timers');
|
||||
$battery->attributes['ZustandBezogeneEnergie'] = 123.456;
|
||||
$battery->attributes['EnergieStandZeit'] = 123456789;
|
||||
$before = [$battery->properties, $battery->attributes, $battery->buffers];
|
||||
foreach ($expected as $name => $call) {
|
||||
$timer = $battery->timers[$name];
|
||||
check($timer['interval'] === 0, $name . ' keeps Create interval zero');
|
||||
check(str_starts_with($timer['script'], 'ENELIX_BatterieTimerAktion(')
|
||||
&& !str_contains($timer['script'], 'IPS_RequestAction('), $name . ' uses generated wrapper');
|
||||
$_IPS = ['TARGET' => 44234];
|
||||
eval($timer['script']);
|
||||
check(end($battery->calls) === $call, $name . ' preserves action and value types');
|
||||
}
|
||||
check([$battery->properties, $battery->attributes, $battery->buffers] === $before,
|
||||
'timer entrypoint does not change configuration, energy or buffers');
|
||||
|
||||
$config = ['batteryInstanceId' => 44234, 'managerId' => 17004,
|
||||
'sources' => [['variableId' => 10, 'parentId' => 20, 'ident' => 'grid']]];
|
||||
$json = json_encode($config, JSON_THROW_ON_ERROR);
|
||||
$battery->properties['NetzfahrplanV4RueckmeldungKonfiguration'] = $json;
|
||||
$sample = ['kind' => 'local_physical_feedback', 'checkedAt' => time(),
|
||||
'sourceOldestAt' => time() - 1, 'deviceReadConfirmed' => true, 'batteryW' => -123.0];
|
||||
$validBuffers = ['V4ConfirmedFeedback' => json_encode($sample, JSON_THROW_ON_ERROR),
|
||||
'V4ConfirmedFeedbackConfig' => hash('sha256', $json),
|
||||
'V4ConfirmedFeedbackMono' => (string) (hrtime(true) / 1e9)];
|
||||
$battery->buffers = $validBuffers;
|
||||
check($battery->GetV4BatterieRueckmeldungCache() === $validBuffers['V4ConfirmedFeedback'],
|
||||
'cache-only API returns original confirmed sample and timestamps');
|
||||
check($battery->buffers === $validBuffers, 'cache inspection never renews buffers');
|
||||
foreach ([time() - 3, time() + 10] as $invalidTime) {
|
||||
$battery->buffers = $validBuffers;
|
||||
$battery->buffers['V4ConfirmedFeedback'] = json_encode(array_replace($sample, ['checkedAt' => $invalidTime]));
|
||||
rejects(fn() => $battery->GetV4BatterieRueckmeldungCache(), 'feedback_confirmed_cache_stale_or_invalid');
|
||||
}
|
||||
$battery->buffers = $validBuffers;
|
||||
$battery->buffers['V4ConfirmedFeedbackMono'] = (string) (hrtime(true) / 1e9 - 3);
|
||||
rejects(fn() => $battery->GetV4BatterieRueckmeldungCache(), 'feedback_confirmed_cache_stale_or_invalid');
|
||||
$battery->buffers = $validBuffers;
|
||||
$battery->buffers['V4ConfirmedFeedbackConfig'] = 'changed';
|
||||
rejects(fn() => $battery->GetV4BatterieRueckmeldungCache(), 'feedback_confirmed_cache_stale_or_invalid');
|
||||
$battery->buffers = $validBuffers;
|
||||
$GLOBALS['testParent'] = 99;
|
||||
rejects(fn() => $battery->GetV4BatterieRueckmeldungCache(), 'feedback_cache_source_changed');
|
||||
check($GLOBALS['deviceCalls'] === 0, 'all cache checks perform zero device reads');
|
||||
echo "TOTAL $count offline battery timer/cache contract checks passed. Native scheduler not mocked as proof.\n";
|
||||
Reference in New Issue
Block a user