Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions backend/app/Console/Commands/MqttListenCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,20 +245,20 @@ private function buildSettings(mixed $cfg): ConnectionSettings
->setReconnectAutomatically(false);

if ($cfg->username) {
$settings->setUsername($cfg->username);
$settings = $settings->setUsername($cfg->username);
}

$password = $cfg->getPassword();
if ($password) {
$settings->setPassword($password);
$settings = $settings->setPassword($password);
}

if ($cfg->use_tls) {
$settings->setUseTls(true);
$settings = $settings->setUseTls(true);
if ($cfg->ca_cert) {
$caFile = tempnam(sys_get_temp_dir(), 'mqtt_ca_');
file_put_contents($caFile, $cfg->ca_cert);
$settings->setTlsCertificateAuthorityFile($caFile);
$settings = $settings->setTlsCertificateAuthorityFile($caFile);
}
}

Expand Down
93 changes: 93 additions & 0 deletions backend/tests/Unit/Connectivity/MqttConnectionSettingsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Tests\Unit\Connectivity;

use App\Console\Commands\MqttListenCommand;
use App\Models\MqttConnection;
use PhpMqtt\Client\ConnectionSettings;
use ReflectionMethod;
use Tests\TestCase;

/**
* The credentials and TLS options saved on a device must reach the broker.
*
* php-mqtt's ConnectionSettings is immutable — every setter clones and returns
* the copy — so calling one without keeping the result is a no-op that nothing
* reports. A broker with anonymous access disabled then refuses the listener as
* "not authorised" while the panel shows a username that was never sent.
*
* buildSettings() is private, so the test reaches it by reflection rather than
* widening the command's API for a test's sake.
*/
class MqttConnectionSettingsTest extends TestCase
{
private function buildSettings(MqttConnection $cfg): ConnectionSettings
{
$method = new ReflectionMethod(MqttListenCommand::class, 'buildSettings');
$method->setAccessible(true);

return $method->invoke(app(MqttListenCommand::class), $cfg);
}

private function connection(array $attributes = []): MqttConnection
{
$cfg = new MqttConnection(array_merge([
'broker_host' => 'broker.local',
'broker_port' => 1883,
'keep_alive_seconds' => 60,
'connect_timeout' => 5,
], $attributes));

$cfg->machine_connection_id = 1;

return $cfg;
}

public function test_a_username_and_password_reach_the_connection(): void
{
$cfg = $this->connection(['username' => 'openmes']);
$cfg->password = 'secret';

$settings = $this->buildSettings($cfg);

$this->assertSame('openmes', $settings->getUsername());
$this->assertSame('secret', $settings->getPassword());
}

public function test_an_anonymous_broker_is_left_without_credentials(): void
{
$settings = $this->buildSettings($this->connection());

$this->assertNull($settings->getUsername());
$this->assertNull($settings->getPassword());
}

public function test_tls_and_its_certificate_authority_reach_the_connection(): void
{
$cfg = $this->connection([
'use_tls' => true,
'ca_cert' => "-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----",
]);

$settings = $this->buildSettings($cfg);

$this->assertTrue($settings->shouldUseTls());

$caFile = $settings->getTlsCertificateAuthorityFile();
$this->assertNotNull($caFile);
$this->assertStringContainsString('BEGIN CERTIFICATE', file_get_contents($caFile));

@unlink($caFile);
}

public function test_the_timings_saved_on_the_device_are_kept(): void
{
$settings = $this->buildSettings($this->connection([
'keep_alive_seconds' => 15,
'connect_timeout' => 9,
]));

$this->assertSame(15, $settings->getKeepAliveInterval());
$this->assertSame(9, $settings->getConnectTimeout());
}
}
30 changes: 29 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,15 @@ services:
DB_DATABASE: ${POSTGRES_DB:-openmmes}
DB_USERNAME: ${POSTGRES_USER:-openmmes_user}
DB_PASSWORD: ${POSTGRES_PASSWORD:-openmmes_secret}
BROADCAST_CONNECTION: log
# Live sync: CollectionChanged is ShouldBroadcastNow, so it is sent by the
# process that writes the row — here, machine counts arriving over MQTT.
BROADCAST_CONNECTION: reverb
REVERB_APP_ID: ${REVERB_APP_ID:-openmes}
REVERB_APP_KEY: ${REVERB_APP_KEY:-openmeskey}
REVERB_APP_SECRET: ${REVERB_APP_SECRET:-openmessecret}
REVERB_HOST: ${REVERB_HOST:-reverb}
REVERB_PORT: ${REVERB_PORT:-8080}
REVERB_SCHEME: ${REVERB_SCHEME:-http}
depends_on:
postgres:
condition: service_healthy
Expand Down Expand Up @@ -243,6 +251,17 @@ services:
DB_DATABASE: ${POSTGRES_DB:-openmmes}
DB_USERNAME: ${POSTGRES_USER:-openmmes_user}
DB_PASSWORD: ${POSTGRES_PASSWORD:-openmmes_secret}
# Live sync: same reason as mqtt-listener — this process writes the
# counter readings, so it is the one that has to reach Reverb. Without
# these the default connection is still reverb, but with no host it
# dials https://:443 and the delta is silently dropped.
BROADCAST_CONNECTION: reverb
REVERB_APP_ID: ${REVERB_APP_ID:-openmes}
REVERB_APP_KEY: ${REVERB_APP_KEY:-openmeskey}
REVERB_APP_SECRET: ${REVERB_APP_SECRET:-openmessecret}
REVERB_HOST: ${REVERB_HOST:-reverb}
REVERB_PORT: ${REVERB_PORT:-8080}
REVERB_SCHEME: ${REVERB_SCHEME:-http}
depends_on:
postgres:
condition: service_healthy
Expand Down Expand Up @@ -314,6 +333,15 @@ services:
DB_DATABASE: ${POSTGRES_DB:-openmmes}
DB_USERNAME: ${POSTGRES_USER:-openmmes_user}
DB_PASSWORD: ${POSTGRES_PASSWORD:-openmmes_secret}
# Live sync: queued ShouldBroadcast events are sent from this process,
# not from backend. Same defaulting trap as the listeners above.
BROADCAST_CONNECTION: reverb
REVERB_APP_ID: ${REVERB_APP_ID:-openmes}
REVERB_APP_KEY: ${REVERB_APP_KEY:-openmeskey}
REVERB_APP_SECRET: ${REVERB_APP_SECRET:-openmessecret}
REVERB_HOST: ${REVERB_HOST:-reverb}
REVERB_PORT: ${REVERB_PORT:-8080}
REVERB_SCHEME: ${REVERB_SCHEME:-http}
depends_on:
postgres:
condition: service_healthy
Expand Down
Loading