diff --git a/backend/app/Console/Commands/MqttListenCommand.php b/backend/app/Console/Commands/MqttListenCommand.php index 93995b44..abb655f9 100644 --- a/backend/app/Console/Commands/MqttListenCommand.php +++ b/backend/app/Console/Commands/MqttListenCommand.php @@ -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); } } diff --git a/backend/tests/Unit/Connectivity/MqttConnectionSettingsTest.php b/backend/tests/Unit/Connectivity/MqttConnectionSettingsTest.php new file mode 100644 index 00000000..d6833512 --- /dev/null +++ b/backend/tests/Unit/Connectivity/MqttConnectionSettingsTest.php @@ -0,0 +1,93 @@ +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()); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 4ecac4cf..941cbaf7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 @@ -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 @@ -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