390 lines
11 KiB
PHP
390 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
usesApiSuite();
|
|
|
|
it('validates broker sessions and ingests presence, telemetry, logs, and shell lifecycle data', function (): void {
|
|
$department = api_fixtures()->createDepartment([
|
|
'name' => 'Edge Broker Department',
|
|
]);
|
|
$session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
|
|
$gateway = api_fixtures()->createClaimedEdgeGateway([
|
|
'department_id' => (int)$department['id'],
|
|
'label' => 'Broker Gateway',
|
|
]);
|
|
|
|
$validateGateway = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/validate',
|
|
['token' => (string)$gateway['agent_token']],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$validateGateway
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($validateGateway->data())
|
|
->toHaveKey('gateway_id', (int)$gateway['id'])
|
|
->toHaveKey('department_id', (int)$department['id']);
|
|
|
|
$presence = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
|
|
[
|
|
'status' => 'connected',
|
|
'connection_id' => 'broker-presence-1',
|
|
'metadata' => [
|
|
'transport' => 'ws',
|
|
],
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$presence
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($presence->data())
|
|
->toHaveKey('connected', true)
|
|
->toHaveKey('connection_id', 'broker-presence-1');
|
|
|
|
$telemetry = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/telemetry',
|
|
[
|
|
'status' => 'ONLINE',
|
|
'metadata' => [
|
|
'system_metrics' => [
|
|
'cpu_load' => 0.42,
|
|
'memory_mb' => 512,
|
|
],
|
|
],
|
|
'inventory' => edge_broker_test_inventory('telemetry'),
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$telemetry
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($telemetry->data()['metadata']['system_metrics']['cpu_load'] ?? null)
|
|
->toBe(0.42);
|
|
|
|
$logEntry = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/logs',
|
|
[
|
|
'level' => 'INFO',
|
|
'stream' => 'agent',
|
|
'source' => 'BROKER',
|
|
'message' => 'Broker forwarded a live gateway log.',
|
|
'context' => [
|
|
'source' => 'broker-test',
|
|
],
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$logEntry
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($logEntry->data())
|
|
->toHaveKey('message', 'Broker forwarded a live gateway log.');
|
|
|
|
$streamSession = api_client()->post(
|
|
'/edge-gateways/' . (int)$gateway['id'] . '/stream-session',
|
|
['scopes' => ['logs', 'statistics', 'tasks']],
|
|
$session['headers']
|
|
);
|
|
|
|
$streamSession
|
|
->assertStatus(201)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$streamValidation = api_client()->post(
|
|
'/edge-agent/internal/browser-streams/validate',
|
|
['token' => (string)$streamSession->data()['token']],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$streamValidation
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($streamValidation->data())
|
|
->toHaveKey('session_type', 'gateway-stream')
|
|
->toHaveKey('gateway_id', (int)$gateway['id']);
|
|
|
|
$shellSession = api_client()->post(
|
|
'/edge-gateways/' . (int)$gateway['id'] . '/shell-sessions',
|
|
['reason' => 'Broker shell validation'],
|
|
$session['headers']
|
|
);
|
|
|
|
$shellSession
|
|
->assertStatus(201)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$shellToken = (string)$shellSession->data()['token'];
|
|
|
|
$validateShell = api_client()->post(
|
|
'/edge-agent/internal/shell-sessions/validate',
|
|
['token' => $shellToken],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$validateShell
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($validateShell->data())
|
|
->toHaveKey('status', 'PENDING');
|
|
|
|
$openedShell = api_client()->post(
|
|
'/edge-agent/internal/shell-sessions/opened',
|
|
[
|
|
'token' => $shellToken,
|
|
'connection_id' => 'shell-connection-1',
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$openedShell
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($openedShell->data())
|
|
->toHaveKey('status', 'OPEN')
|
|
->toHaveKey('connection_id', 'shell-connection-1');
|
|
|
|
$closedShell = api_client()->post(
|
|
'/edge-agent/internal/shell-sessions/close',
|
|
[
|
|
'token' => $shellToken,
|
|
'transcript' => "edge-broker-shell\n",
|
|
'reason' => 'agent_exit',
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$closedShell
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($closedShell->data())
|
|
->toHaveKey('status', 'COMPLETED')
|
|
->and($closedShell->data()['transcript'] ?? null)
|
|
->toBe("edge-broker-shell\n");
|
|
|
|
$logsPage = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/logs', $session['headers']);
|
|
|
|
$logsPage
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(collect_gateway_messages($logsPage->data()['log_entries'] ?? []))
|
|
->toContain('Broker forwarded a live gateway log.');
|
|
expect(collect_gateway_messages($logsPage->data()['timeline'] ?? []))
|
|
->toContain('GATEWAY_SHELL_SESSION_OPENED')
|
|
->toContain('GATEWAY_SHELL_SESSION_CLOSED');
|
|
expect($logsPage->data()['shell_sessions'][0]['transcript'] ?? null)
|
|
->toBe("edge-broker-shell\n");
|
|
|
|
$statisticsPage = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/statistics', $session['headers']);
|
|
|
|
$statisticsPage
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($statisticsPage->data())
|
|
->toHaveKey('system_metrics')
|
|
->and($statisticsPage->data()['system_metrics']['cpu_load'] ?? null)
|
|
->toBe(0.42);
|
|
});
|
|
|
|
it('builds broker backlog and completes gateway operations through broker endpoints', function (): void {
|
|
$department = api_fixtures()->createDepartment([
|
|
'name' => 'Edge Broker Backlog Department',
|
|
]);
|
|
$session = api_fixtures()->createEdgeOperatorSession((int)$department['id']);
|
|
$gateway = api_fixtures()->createClaimedEdgeGateway([
|
|
'department_id' => (int)$department['id'],
|
|
'label' => 'Broker Backlog Gateway',
|
|
]);
|
|
|
|
$queuedOperation = api_client()->post('/edge-gateways/' . (int)$gateway['id'] . '/operations', [
|
|
'type' => 'DISCOVERY',
|
|
'request' => [
|
|
'inventory' => edge_broker_test_inventory('backlog'),
|
|
],
|
|
], $session['headers']);
|
|
|
|
$queuedOperation
|
|
->assertStatus(201)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$operationId = (int)($queuedOperation->data()['operation']['id'] ?? 0);
|
|
expect($operationId)->toBeGreaterThan(0);
|
|
|
|
$backlog = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/backlog',
|
|
['agent_instance_id' => 'broker-agent-1'],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$backlog
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($backlog->data()['dispatch'] ?? [])
|
|
->toBeArray()
|
|
->not->toBeEmpty()
|
|
->and($backlog->data()['dispatch'][0]['type'] ?? null)
|
|
->toBe('TASK_DISPATCH')
|
|
->and((int)($backlog->data()['dispatch'][0]['operation']['id'] ?? 0))
|
|
->toBe($operationId);
|
|
|
|
$event = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/events',
|
|
[
|
|
'level' => 'INFO',
|
|
'code' => 'BROKER_EXECUTING',
|
|
'message' => 'Broker is executing the operation.',
|
|
'context' => [
|
|
'progress' => 50,
|
|
],
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$event
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$complete = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/complete',
|
|
[
|
|
'ok' => true,
|
|
'result' => [
|
|
'inventory' => edge_broker_test_inventory('completed'),
|
|
],
|
|
],
|
|
edge_test_broker_headers()
|
|
);
|
|
|
|
$complete
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($complete->data())
|
|
->toHaveKey('status', 'COMPLETED');
|
|
|
|
$operations = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/operations', $session['headers']);
|
|
|
|
$operations
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(($operations->data()[0]['status'] ?? null))
|
|
->toBe('COMPLETED');
|
|
|
|
$events = api_client()->get(
|
|
'/edge-gateways/' . (int)$gateway['id'] . '/operations/' . $operationId . '/events',
|
|
$session['headers']
|
|
);
|
|
|
|
$events
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(collect_gateway_messages($events->data()))
|
|
->toContain('Broker is executing the operation.')
|
|
->toContain('Operation completed successfully');
|
|
|
|
$tasksPage = api_client()->get('/edge-gateways/' . (int)$gateway['id'] . '/tasks', $session['headers']);
|
|
|
|
$tasksPage
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($tasksPage->data()['recent_operations_summary']['completed'] ?? 0)
|
|
->toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
it('rejects invalid edge broker shared secrets', function (): void {
|
|
$department = api_fixtures()->createDepartment([
|
|
'name' => 'Edge Broker Forbidden Department',
|
|
]);
|
|
$gateway = api_fixtures()->createClaimedEdgeGateway([
|
|
'department_id' => (int)$department['id'],
|
|
]);
|
|
|
|
$response = api_client()->post(
|
|
'/edge-agent/internal/gateways/' . (int)$gateway['id'] . '/presence',
|
|
['status' => 'connected'],
|
|
['X-Edge-Broker-Secret' => 'wrong-secret']
|
|
);
|
|
|
|
$response
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Invalid edge broker secret');
|
|
});
|
|
|
|
function edge_broker_test_inventory(string $suffix): array
|
|
{
|
|
return [[
|
|
'device_id' => 'broker-' . $suffix,
|
|
'local_ip' => '10.40.50.60',
|
|
'model' => 'TruckWash Edge Broker',
|
|
'channel_count' => 1,
|
|
'online' => true,
|
|
'capabilities' => [
|
|
'relay_commands' => true,
|
|
],
|
|
'metadata' => [
|
|
'hostname' => 'broker-' . $suffix,
|
|
],
|
|
]];
|
|
}
|
|
|
|
/**
|
|
* @param mixed $items
|
|
* @return array<int, string>
|
|
*/
|
|
function collect_gateway_messages(mixed $items): array
|
|
{
|
|
if (!is_array($items)) {
|
|
return [];
|
|
}
|
|
|
|
$messages = [];
|
|
foreach ($items as $item) {
|
|
if (is_array($item) && isset($item['message']) && is_string($item['message'])) {
|
|
$messages[] = $item['message'];
|
|
}
|
|
}
|
|
|
|
return $messages;
|
|
}
|