Add requestBooleanFlag helper and enhance session synchronization logic
- Introduce `requestBooleanFlag` method for consistent boolean parameter handling with default values. - Add `activate_machine` and `sync_relay_state` parameters to `synchronizeSession` for more flexible relay and machine activation control. - Update methods, routes, and tests to integrate the new session synchronization parameters effectively. - Enhance debugging support with additional metadata in simulation and payload captures.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -394,6 +394,73 @@ class selfserve_studio_graph
|
||||
return $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $payload
|
||||
* @param array<string,bool> $permissions
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function simulateGraph(int $departmentId, array $payload, ?int $userId = null, array $permissions = []): array
|
||||
{
|
||||
$laneId = (int)($payload['lane_id'] ?? 0);
|
||||
if ($laneId <= 0) {
|
||||
throw new \RuntimeException('lane_id is required for studio simulation.');
|
||||
}
|
||||
|
||||
$configSource = strtolower(trim((string)($payload['config_source'] ?? 'draft')));
|
||||
if (!in_array($configSource, ['draft', 'published'], true)) {
|
||||
$configSource = 'draft';
|
||||
}
|
||||
|
||||
$versioning = new selfserve_config_versioning();
|
||||
if ($configSource === 'published') {
|
||||
$version = $versioning->getPublishedConfig($departmentId);
|
||||
$config = is_array($version['config'] ?? null) ? (array)$version['config'] : $versioning->snapshotLegacyConfig($departmentId);
|
||||
$versionId = isset($version['version_id']) ? (int)$version['version_id'] : null;
|
||||
} else {
|
||||
$version = $versioning->ensureDraftFromLegacy($departmentId, $userId, false);
|
||||
$config = is_array($version['config'] ?? null) ? (array)$version['config'] : $versioning->snapshotLegacyConfig($departmentId);
|
||||
$versionId = isset($version['id']) ? (int)$version['id'] : null;
|
||||
}
|
||||
|
||||
$includeHardware = filter_var($payload['include_hardware'] ?? true, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
|
||||
$includeHardware = $includeHardware !== false;
|
||||
$gatewayWorkspace = ($includeHardware && ($permissions['modules_shelly_config'] ?? false))
|
||||
? $this->buildGatewayWorkspace($departmentId)
|
||||
: [
|
||||
'gateways' => [],
|
||||
'relays' => [],
|
||||
'lanes' => [],
|
||||
'issues' => [],
|
||||
'actions' => [],
|
||||
'restricted' => !$includeHardware ? false : true,
|
||||
];
|
||||
$lookups = $this->buildLookups($departmentId, $config, $gatewayWorkspace);
|
||||
$graph = $this->buildGraphFromConfig($config, [
|
||||
'department_id' => $departmentId,
|
||||
'lookups' => $lookups,
|
||||
'gateway_workspace' => $gatewayWorkspace,
|
||||
]);
|
||||
|
||||
return (new selfserve_wash_flow())->previewStudioSimulation(
|
||||
$departmentId,
|
||||
$laneId,
|
||||
(string)($payload['reg'] ?? ''),
|
||||
array_key_exists('customer_number', $payload) ? $this->nullableInt($payload['customer_number']) : null,
|
||||
array_key_exists('vehicle_type_id', $payload) ? $this->nullableInt($payload['vehicle_type_id']) : null,
|
||||
[
|
||||
'mode' => 'full_dry_run',
|
||||
'config_source' => $configSource,
|
||||
'config_payload' => $config,
|
||||
'config_version_id' => $versionId,
|
||||
'answer_overrides' => is_array($payload['answer_overrides'] ?? null) ? (array)$payload['answer_overrides'] : [],
|
||||
'include_hardware' => $includeHardware,
|
||||
'lookups' => $lookups,
|
||||
'gateway_workspace' => $gatewayWorkspace,
|
||||
'graph' => $graph,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $payload
|
||||
* @return array<string,mixed>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -260,6 +260,33 @@ trait selfserve_lane_relay_controller_t
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the services currently visible to the user without mutating hardware.
|
||||
*
|
||||
* The user wash start flow calls this before the user confirms lane and wash type.
|
||||
* Hardware activation remains owned by START / explicit relay endpoints.
|
||||
*
|
||||
* @param array<int,mixed> $allowedServices
|
||||
* @return array{
|
||||
* machine_visible: bool,
|
||||
* relay_action: string,
|
||||
* relay_target_on: bool
|
||||
* }
|
||||
*/
|
||||
public function setAllowedServicesFromVisibleTasks(array $allowedServices): array
|
||||
{
|
||||
$normalizedServices = $this->normalizeVisibleServiceNames($allowedServices);
|
||||
$this->setLaneCache($this->id, self::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES, $normalizedServices);
|
||||
|
||||
$machineVisible = in_array(selfserve_lane_services::MACHINE->name, $normalizedServices, true);
|
||||
|
||||
return [
|
||||
'machine_visible' => $machineVisible,
|
||||
'relay_action' => 'cache_only',
|
||||
'relay_target_on' => $machineVisible,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $services
|
||||
* @return string[]
|
||||
|
||||
@@ -4674,6 +4674,22 @@ paths:
|
||||
customer_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
vehicle_type:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Optional product/vehicle type override used when refreshing the self-serve summary.
|
||||
vehicle_type_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Alias for vehicle_type.
|
||||
activate_machine:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Whether the session synchronization may enable the machine relay. User wash-start saves answers with false.
|
||||
sync_relay_state:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Whether the answer mutation should synchronize live relay state.
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully added vehicle condition
|
||||
@@ -4718,6 +4734,22 @@ paths:
|
||||
customer_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
vehicle_type:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Optional product/vehicle type override used when refreshing the self-serve summary.
|
||||
vehicle_type_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: Alias for vehicle_type.
|
||||
activate_machine:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Whether the session synchronization may enable the machine relay.
|
||||
sync_relay_state:
|
||||
type: boolean
|
||||
default: true
|
||||
description: Whether the mutation should synchronize live relay state.
|
||||
responses:
|
||||
'200':
|
||||
description: Successfully updated vehicle condition
|
||||
@@ -5304,13 +5336,34 @@ paths:
|
||||
reg: { type: string }
|
||||
customer_number: { type: integer, nullable: true }
|
||||
vehicle_type_id: { type: integer, nullable: true }
|
||||
config_source:
|
||||
type: string
|
||||
enum: [draft, published]
|
||||
default: draft
|
||||
answer_overrides:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required: [question_id]
|
||||
properties:
|
||||
question_id: { type: integer }
|
||||
value:
|
||||
type: boolean
|
||||
nullable: true
|
||||
include_hardware:
|
||||
type: boolean
|
||||
default: true
|
||||
mode:
|
||||
type: string
|
||||
enum: [full_dry_run]
|
||||
default: full_dry_run
|
||||
responses:
|
||||
'200':
|
||||
description: Simulator result
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SelfserveVehicleAllowedResponse'
|
||||
$ref: '#/components/schemas/SelfserveStudioSimulationResponse'
|
||||
|
||||
/department/selfserve/studio/publish:
|
||||
post:
|
||||
@@ -15287,6 +15340,75 @@ components:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
SelfserveStudioSimulationDebug:
|
||||
type: object
|
||||
required: [summary, parameters, stages, questions, conditions, rules, tasks, hardware, graph_annotations, recommendations]
|
||||
properties:
|
||||
summary:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
parameters:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
stages:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
questions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
conditions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
hardware:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
graph_annotations:
|
||||
type: object
|
||||
properties:
|
||||
nodes:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
edges:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
recommendations:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
|
||||
SelfserveStudioSimulationResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SelfserveVehicleAllowedResponse'
|
||||
- type: object
|
||||
properties:
|
||||
simulator_version: { type: integer }
|
||||
dry_run: { type: boolean, enum: [true] }
|
||||
mode: { type: string, enum: [full_dry_run] }
|
||||
config_source: { type: string, enum: [draft, published] }
|
||||
debug:
|
||||
$ref: '#/components/schemas/SelfserveStudioSimulationDebug'
|
||||
|
||||
SelfserveVehicleAllowedResponse:
|
||||
type: object
|
||||
properties:
|
||||
@@ -15330,6 +15452,13 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SelfserveWashSession'
|
||||
nullable: true
|
||||
config_source:
|
||||
type: string
|
||||
nullable: true
|
||||
evaluation_trace:
|
||||
type: object
|
||||
nullable: true
|
||||
additionalProperties: true
|
||||
|
||||
SelfserveWashSummary:
|
||||
type: object
|
||||
|
||||
@@ -93,11 +93,11 @@ class departmentSelfserveStudioRoute
|
||||
$this->assertDepartmentAccess($user, $departmentId);
|
||||
|
||||
try {
|
||||
$result = (new selfserve_wash_flow())->previewVehicleEligibility(
|
||||
$laneId,
|
||||
(string)self::getParameter('reg'),
|
||||
self::isParametersSet(['customer_number']) ? (int)self::getParameter('customer_number') : null,
|
||||
self::isParametersSet(['vehicle_type_id']) ? (int)self::getParameter('vehicle_type_id') : null
|
||||
$result = (new selfserve_studio_graph())->simulateGraph(
|
||||
$departmentId,
|
||||
self::getParametersAsArray(),
|
||||
(int)$user->id,
|
||||
$this->studioPermissions()
|
||||
);
|
||||
(new logs_o())->add('selfserve_studio', $departmentId, 1, $user->id, 'SIMULATE_STUDIO_GRAPH', 'Simulated self-serve studio graph');
|
||||
$response->success($result);
|
||||
|
||||
@@ -243,6 +243,8 @@ class departmentSelfserveVehicleConditionsRoute
|
||||
$response->error('Missing required fields', 400);
|
||||
}
|
||||
$vehicle_type_id = $this->resolveVehicleTypeIdFromRequest();
|
||||
$activate_machine = $this->requestBooleanFlag('activate_machine', true);
|
||||
$sync_relay_state = $this->requestBooleanFlag('sync_relay_state', true);
|
||||
|
||||
if ($has_global) {
|
||||
$customer_id = $response->isRequestParameterSet('customer_id') ? (int)$response->getRequestParameter('customer_id') : null;
|
||||
@@ -259,7 +261,7 @@ class departmentSelfserveVehicleConditionsRoute
|
||||
try {
|
||||
$condition_o = new department_selfserve_vehicle_conditions_o();
|
||||
$condition_o->add($department, $lane, $reg, $question, $value, $customer_id);
|
||||
$summary = $this->getWashFlow()->synchronizeSession($lane, $reg, $customer_id, true, $vehicle_type_id, true);
|
||||
$summary = $this->getWashFlow()->synchronizeSession($lane, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state);
|
||||
(new logs_o())->add('department_selfserve_vehicle_conditions', 'global', 1, $user->id, 'ADD_VEHICLE_CONDITION', 'User added department self-serve vehicle condition ' . $condition_o->id);
|
||||
$response->success([
|
||||
'condition' => $condition_o->asArray(),
|
||||
@@ -351,15 +353,17 @@ class departmentSelfserveVehicleConditionsRoute
|
||||
$condition_o->customer_id->update($new_customer_id);
|
||||
}
|
||||
$vehicle_type_id = $this->resolveVehicleTypeIdFromRequest();
|
||||
$activate_machine = $this->requestBooleanFlag('activate_machine', true);
|
||||
$sync_relay_state = $this->requestBooleanFlag('sync_relay_state', true);
|
||||
|
||||
try {
|
||||
$summary = $this->getWashFlow()->synchronizeSession(
|
||||
(int)$condition_o->lane->value(),
|
||||
(string)$condition_o->reg->value(),
|
||||
$condition_o->customer_id->value() === null ? null : (int)$condition_o->customer_id->value(),
|
||||
true,
|
||||
$activate_machine,
|
||||
$vehicle_type_id,
|
||||
true
|
||||
$sync_relay_state
|
||||
);
|
||||
(new logs_o())->add('department_selfserve_vehicle_conditions', 'global', 1, $user->id, 'UPDATE_VEHICLE_CONDITION', 'User updated department self-serve vehicle condition ' . $id);
|
||||
$response->success([
|
||||
@@ -417,11 +421,13 @@ class departmentSelfserveVehicleConditionsRoute
|
||||
$reg = (string)$condition_o->reg->value();
|
||||
$customer_id = $condition_o->customer_id->value() === null ? null : (int)$condition_o->customer_id->value();
|
||||
$vehicle_type_id = $this->resolveVehicleTypeIdFromRequest();
|
||||
$activate_machine = $this->requestBooleanFlag('activate_machine', true);
|
||||
$sync_relay_state = $this->requestBooleanFlag('sync_relay_state', true);
|
||||
|
||||
$condition_o->delete();
|
||||
|
||||
try {
|
||||
$summary = $this->getWashFlow()->synchronizeSession($lane_id, $reg, $customer_id, true, $vehicle_type_id, true);
|
||||
$summary = $this->getWashFlow()->synchronizeSession($lane_id, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state);
|
||||
} catch (\Throwable) {
|
||||
$summary = null;
|
||||
}
|
||||
@@ -506,6 +512,31 @@ class departmentSelfserveVehicleConditionsRoute
|
||||
return $questions === [] && $tasks === [];
|
||||
}
|
||||
|
||||
private function requestBooleanFlag(string $parameter, bool $default): bool
|
||||
{
|
||||
if (!$this->isParametersSet([$parameter])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = $this->getParameter($parameter);
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_int($value)) {
|
||||
return $value !== 0;
|
||||
}
|
||||
|
||||
$normalized = strtolower(trim((string)$value));
|
||||
if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
|
||||
return true;
|
||||
}
|
||||
if (in_array($normalized, ['0', 'false', 'no', 'off'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
private function assertLaneAccess(object $user, int $laneId, bool $hasGlobalPermission): department_lanes_o
|
||||
{
|
||||
global $response;
|
||||
|
||||
@@ -522,8 +522,7 @@ class moduleSelfServeRoute
|
||||
}
|
||||
// Persist on lane cache (overwrites previous allowed services)
|
||||
try {
|
||||
$this->applyShellyTransportOverride($lane);
|
||||
$relay_sync = $lane->syncMachineRelayFromVisibleServices($allowed_services, true);
|
||||
$relay_sync = $lane->setAllowedServicesFromVisibleTasks($allowed_services);
|
||||
$response->success([
|
||||
'lane_id' => $lane_id,
|
||||
'allowed_services' => $allowed_services,
|
||||
|
||||
@@ -560,6 +560,20 @@ it('does not enable MACHINE relay when allowEnable is false even if MACHINE is v
|
||||
expect($harness->getShellyCallCount('/v2/devices/api/set/switch'))->toBe(0);
|
||||
});
|
||||
|
||||
it('persists allowed services without relay writes for pre-start wash setup', function (): void {
|
||||
$harness = selfserve_lane_shelly_test_harness();
|
||||
|
||||
$result = $harness->setAllowedServicesFromVisibleTasks(['machine']);
|
||||
|
||||
expect($result)->toMatchArray([
|
||||
'machine_visible' => true,
|
||||
'relay_action' => 'cache_only',
|
||||
'relay_target_on' => true,
|
||||
]);
|
||||
expect($harness->getLaneCache($harness->id, $harness::CACHE_SELFSERVE_LANE_KEY_ALLOWED_SERVICES))->toBe(['MACHINE']);
|
||||
expect($harness->getShellyCallCount('/v2/devices/api/set/switch'))->toBe(0);
|
||||
});
|
||||
|
||||
it('returns disabled no-op from machine relay visibility sync when department self-serve is disabled', function (): void {
|
||||
$harness = selfserve_lane_shelly_test_harness();
|
||||
$harness->selfServeEnabled = false;
|
||||
|
||||
@@ -118,7 +118,7 @@ it('applies Shelly transport overrides across self-serve relay side-effect route
|
||||
expect($moduleSelfServeRoute)->not->toBeFalse();
|
||||
expect(substr_count($moduleSelfServeRoute, 'applyShellyTransportOverride($lane)'))->toBeGreaterThanOrEqual(17);
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->execute($command, $args);');
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->syncMachineRelayFromVisibleServices($allowed_services, true);');
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->setAllowedServicesFromVisibleTasks($allowed_services);');
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->open($gate, $toggle_after);');
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->turnOnRelay(selfserve_lane_relay::MACHINE, $duration);');
|
||||
expect($moduleSelfServeRoute)->toContain('$lane->turnOnRelay(selfserve_lane_relay::MACHINE_PROGRAM_PICKER, $duration);');
|
||||
@@ -136,7 +136,8 @@ it('wires allowed services route through machine relay visibility sync', functio
|
||||
|
||||
expect($moduleSelfServeRoute)->not->toBeFalse();
|
||||
expect($moduleSelfServeRoute)->toContain('/modules/self-serve/lane/services/allowed');
|
||||
expect($moduleSelfServeRoute)->toContain('syncMachineRelayFromVisibleServices($allowed_services, true)');
|
||||
expect($moduleSelfServeRoute)->toContain('setAllowedServicesFromVisibleTasks($allowed_services)');
|
||||
expect($moduleSelfServeRoute)->not->toContain('syncMachineRelayFromVisibleServices($allowed_services, true)');
|
||||
expect($moduleSelfServeRoute)->toContain("'relay_sync' => \$relay_sync");
|
||||
});
|
||||
|
||||
@@ -277,8 +278,9 @@ it('wires vehicle type override into self-serve preview and synchronization rout
|
||||
expect($vehicleConditionsRoute)->toContain('shouldRefreshSummaryForVehicleType');
|
||||
expect($vehicleConditionsRoute)->toContain('previewVehicleEligibility($lane_id, $reg, $customer_number, $vehicle_type_id)');
|
||||
expect($vehicleConditionsRoute)->toContain('synchronizeSession($lane_id, $reg, $customer_number, false, $vehicle_type_id, false)');
|
||||
expect($vehicleConditionsRoute)->toContain('synchronizeSession($lane, $reg, $customer_id, true, $vehicle_type_id, true)');
|
||||
expect($vehicleConditionsRoute)->toContain('synchronizeSession($lane_id, $reg, $customer_id, true, $vehicle_type_id, true)');
|
||||
expect($vehicleConditionsRoute)->toContain('requestBooleanFlag');
|
||||
expect($vehicleConditionsRoute)->toContain('synchronizeSession($lane, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state)');
|
||||
expect($vehicleConditionsRoute)->toContain('synchronizeSession($lane_id, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state)');
|
||||
|
||||
expect($washFlow)->not->toBeFalse();
|
||||
expect($washFlow)->toContain('resolveVehicleTypeId($vehicle, $vehicleTypeIdOverride)');
|
||||
@@ -294,6 +296,8 @@ it('keeps read-only self-serve preview and summary refreshes from touching relay
|
||||
expect($vehicleConditionsRoute)->toContain('$flow->synchronizeSession($lane_id, $reg, $customer_number, false, $vehicle_type_id, false);');
|
||||
expect($vehicleConditionsRoute)->toContain('$summary = $flow->synchronizeSession(');
|
||||
expect($vehicleConditionsRoute)->toContain('$summary = $flow->synchronizeSession($lane_id, $reg, $customer_number, false, $vehicle_type_id, false);');
|
||||
expect($vehicleConditionsRoute)->toContain('$this->getWashFlow()->synchronizeSession($lane, $reg, $customer_id, true, $vehicle_type_id, true);');
|
||||
expect($vehicleConditionsRoute)->toContain('$this->getWashFlow()->synchronizeSession($lane_id, $reg, $customer_id, true, $vehicle_type_id, true);');
|
||||
expect($vehicleConditionsRoute)->toContain('$activate_machine = $this->requestBooleanFlag(\'activate_machine\', true);');
|
||||
expect($vehicleConditionsRoute)->toContain('$sync_relay_state = $this->requestBooleanFlag(\'sync_relay_state\', true);');
|
||||
expect($vehicleConditionsRoute)->toContain('$this->getWashFlow()->synchronizeSession($lane, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state);');
|
||||
expect($vehicleConditionsRoute)->toContain('$this->getWashFlow()->synchronizeSession($lane_id, $reg, $customer_id, $activate_machine, $vehicle_type_id, $sync_relay_state);');
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
app_require('modules/selfserve/classes/selfserve_studio_graph.php');
|
||||
|
||||
use modules\selfserve\classes\selfserve_studio_graph;
|
||||
use modules\selfserve\classes\selfserve_wash_flow;
|
||||
|
||||
function selfserve_studio_graph_without_constructor(): selfserve_studio_graph
|
||||
{
|
||||
@@ -10,6 +11,12 @@ function selfserve_studio_graph_without_constructor(): selfserve_studio_graph
|
||||
return $reflection->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
function selfserve_wash_flow_without_constructor(): selfserve_wash_flow
|
||||
{
|
||||
$reflection = new ReflectionClass(selfserve_wash_flow::class);
|
||||
return $reflection->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
it('serializes questions, conditions, tasks, scopes, and gateways into one graph', function (): void {
|
||||
$service = selfserve_studio_graph_without_constructor();
|
||||
|
||||
@@ -160,3 +167,173 @@ it('keeps layout loading compatible with native PDO named placeholders', functio
|
||||
expect($source)->toContain(':user_id_filter');
|
||||
expect($source)->toContain(':user_id_sort');
|
||||
});
|
||||
|
||||
it('builds guided simulator debug payload with blockers and canvas annotations', function (): void {
|
||||
$service = selfserve_wash_flow_without_constructor();
|
||||
|
||||
$debug = $service->buildStudioDebugPayload(6, [
|
||||
'lane' => ['id' => 7, 'name' => 'Lane 7'],
|
||||
'machine_type' => ['id' => 1001, 'name' => 'Portal'],
|
||||
'vehicle' => null,
|
||||
'reg' => 'TEST123',
|
||||
'customer_number' => null,
|
||||
'vehicle_type_id' => 2,
|
||||
'answers' => [11 => null],
|
||||
'answer_sources' => [11 => 'override'],
|
||||
'questions' => [],
|
||||
'tasks' => [],
|
||||
'allowed_services' => [],
|
||||
'machine_available' => false,
|
||||
'all_visible_questions_answered' => false,
|
||||
'allowed' => false,
|
||||
'config_version_id' => 90,
|
||||
'config_source' => 'draft',
|
||||
'evaluation_trace' => [
|
||||
'visible_question_ids' => [11],
|
||||
'visibility_condition_results' => [21 => true],
|
||||
'condition_results' => [21 => true],
|
||||
'task_gates' => [
|
||||
['task_id' => 41, 'gate_type' => 'QUESTION', 'gate_ref_id' => 11, 'satisfied' => false],
|
||||
],
|
||||
],
|
||||
'debug_candidates' => [
|
||||
'questions' => [
|
||||
['id' => 11, 'question' => 'Are mirrors folded?', 'condition_id' => 21, 'order_priority' => 1],
|
||||
],
|
||||
'conditions' => [
|
||||
['id' => 21, 'name' => 'Trailer present', 'condition_id' => null],
|
||||
],
|
||||
'rules' => [
|
||||
['id' => 31, 'condition_id' => 21, 'type' => 'IS_TRUE', 'object_type' => 'question', 'object_id' => 11, 'name' => 'Mirror answer'],
|
||||
],
|
||||
'tasks' => [
|
||||
['id' => 41, 'task' => 'Fold mirrors', 'gate_type' => 'QUESTION', 'gate_ref_id' => 11, 'services' => ['MACHINE'], 'buttons' => [1], 'order_priority' => 1],
|
||||
],
|
||||
],
|
||||
], [
|
||||
'lookups' => [
|
||||
'labels' => [
|
||||
'departments' => ['6' => 'Roskilde'],
|
||||
'lanes' => ['7' => 'Lane 7'],
|
||||
'vehicle_types' => ['2' => 'Forvogn'],
|
||||
'machine_types' => ['1001' => 'Portal'],
|
||||
'questions' => ['11' => 'Are mirrors folded?'],
|
||||
'conditions' => ['21' => 'Trailer present'],
|
||||
'rules' => ['31' => 'Mirror answer'],
|
||||
'tasks' => ['41' => 'Fold mirrors'],
|
||||
],
|
||||
],
|
||||
'gateway_workspace' => [
|
||||
'gateways' => [
|
||||
[
|
||||
'id' => 701,
|
||||
'label' => 'Roskilde Edge',
|
||||
'status' => 'ONLINE',
|
||||
'bindings' => [
|
||||
['relay_id' => 'M-7', 'label' => 'Machine relay', 'role' => 'MACHINE', 'services' => ['MACHINE']],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'graph' => [
|
||||
'edges' => [
|
||||
['id' => 'task-gate:question:11:41', 'source' => 'question:11', 'target' => 'task:41', 'label' => 'unlocks'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
expect($debug['summary']['status'])->toBe('blocked')
|
||||
->and($debug['parameters']['config_source'])->toBe('draft')
|
||||
->and($debug['questions'][0]['state'])->toBe('missing')
|
||||
->and($debug['questions'][0]['answer_source'])->toBe('override')
|
||||
->and($debug['tasks'][0]['state'])->toBe('blocked')
|
||||
->and(array_column($debug['recommendations'], 'title'))->toContain('Answer required questions')
|
||||
->and(array_column($debug['recommendations'], 'title'))->toContain('Configure lane machine relay')
|
||||
->and($debug['graph_annotations']['nodes']['question:11']['state'])->toBe('warning')
|
||||
->and($debug['graph_annotations']['nodes']['lane:7']['state'])->toBe('error');
|
||||
});
|
||||
|
||||
it('resolves simulator gateway service bindings from lane relay slots', function (): void {
|
||||
$service = selfserve_wash_flow_without_constructor();
|
||||
|
||||
$debug = $service->buildStudioDebugPayload(6, [
|
||||
'lane' => ['id' => 7, 'name' => 'Lane 7'],
|
||||
'machine_type' => ['id' => 1001, 'name' => 'Portal'],
|
||||
'vehicle' => null,
|
||||
'reg' => 'TEST123',
|
||||
'customer_number' => null,
|
||||
'vehicle_type_id' => 2,
|
||||
'answers' => [11 => true],
|
||||
'answer_sources' => [11 => 'override'],
|
||||
'questions' => [],
|
||||
'tasks' => [
|
||||
['id' => 41, 'task' => 'Start machine', 'services' => ['MACHINE']],
|
||||
],
|
||||
'allowed_services' => ['MACHINE'],
|
||||
'machine_available' => true,
|
||||
'all_visible_questions_answered' => true,
|
||||
'allowed' => true,
|
||||
'config_version_id' => 90,
|
||||
'config_source' => 'draft',
|
||||
'evaluation_trace' => [
|
||||
'visible_question_ids' => [11],
|
||||
'visibility_condition_results' => [],
|
||||
'condition_results' => [],
|
||||
'task_gates' => [
|
||||
['task_id' => 41, 'gate_type' => 'ALWAYS', 'gate_ref_id' => null, 'satisfied' => true],
|
||||
],
|
||||
],
|
||||
'debug_candidates' => [
|
||||
'questions' => [
|
||||
['id' => 11, 'question' => 'Are mirrors folded?', 'condition_id' => null, 'order_priority' => 1],
|
||||
],
|
||||
'conditions' => [],
|
||||
'rules' => [],
|
||||
'tasks' => [
|
||||
['id' => 41, 'task' => 'Start machine', 'gate_type' => 'ALWAYS', 'gate_ref_id' => null, 'services' => ['MACHINE'], 'buttons' => [1], 'order_priority' => 1],
|
||||
],
|
||||
],
|
||||
], [
|
||||
'lookups' => [
|
||||
'labels' => [
|
||||
'departments' => ['6' => 'Roskilde'],
|
||||
'lanes' => ['7' => 'Lane 7'],
|
||||
'vehicle_types' => ['2' => 'Forvogn'],
|
||||
'machine_types' => ['1001' => 'Portal'],
|
||||
'questions' => ['11' => 'Are mirrors folded?'],
|
||||
'tasks' => ['41' => 'Start machine'],
|
||||
],
|
||||
],
|
||||
'gateway_workspace' => [
|
||||
'gateways' => [
|
||||
[
|
||||
'id' => 701,
|
||||
'label' => 'Roskilde Edge',
|
||||
'status' => 'ONLINE',
|
||||
'bindings' => [
|
||||
['relay_id' => 'M-7', 'label' => 'Machine relay'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'lanes' => [
|
||||
[
|
||||
'id' => 7,
|
||||
'relay_slots' => [
|
||||
['relay_id' => 'M-7', 'slot' => 'MACHINE'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'graph' => [
|
||||
'edges' => [
|
||||
['id' => 'task-service:41:MACHINE:701:M-7:0', 'source' => 'task:41', 'target' => 'binding:701:M-7:0', 'label' => 'MACHINE'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
expect($debug['hardware']['missing_service_bindings'])->toBe([])
|
||||
->and($debug['hardware']['service_bindings']['MACHINE'][0]['node_id'])->toBe('binding:701:M-7:0')
|
||||
->and($debug['hardware']['summary'])->toBe('Lane relay and gateway service bindings are ready for the simulated services.')
|
||||
->and($debug['tasks'][0]['relay_bindings'][0]['service'])->toBe('MACHINE')
|
||||
->and(array_column($debug['recommendations'], 'title'))->toBe(['Flow is ready']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user