1<?php
2/*
3 * LocationTest.php
4 *
5 * -Description-
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 *
20 * @package    LibreNMS
21 * @link       http://librenms.org
22 * @copyright  2021 Tony Murray
23 * @author     Tony Murray <murraytony@gmail.com>
24 */
25
26namespace LibreNMS\Tests\Unit;
27
28use App\Models\Device;
29use App\Models\Location;
30use LibreNMS\Config;
31use LibreNMS\Interfaces\Geocoder;
32use LibreNMS\Tests\TestCase;
33use LibreNMS\Util\Dns;
34use Mockery\MockInterface;
35
36class LocationTest extends TestCase
37{
38    public function testCanSetLocation()
39    {
40        $device = Device::factory()->make(); /** @var Device $device */
41        $device->setLocation('Where');
42
43        $this->assertEquals($device->location->location, 'Where');
44        $this->assertNull($device->location->lat);
45        $this->assertNull($device->location->lng);
46
47        $device->setLocation(null);
48        $this->assertNull($device->location);
49    }
50
51    public function testCanNotSetLocation()
52    {
53        $device = Device::factory()->make(); /** @var Device $device */
54        $location = Location::factory()->make();
55
56        $device->override_sysLocation = true;
57        $device->setLocation($location->location);
58        $this->assertNull($device->location);
59    }
60
61    public function testCanSetEncodedLocation()
62    {
63        Config::set('geoloc.dns', false);
64        $device = Device::factory()->make(); /** @var Device $device */
65
66        // valid coords
67        $location = Location::factory()->withCoordinates()->make();
68        $device->setLocation("$location->location [$location->lat,$location->lng]", true);
69        $this->assertEquals("$location->location [$location->lat,$location->lng]", $device->location->location);
70        $this->assertEquals($location->location, $device->location->display());
71        $this->assertEquals($location->lat, $device->location->lat);
72        $this->assertEquals($location->lng, $device->location->lng);
73
74        // with space
75        $location = Location::factory()->withCoordinates()->make();
76        $device->setLocation("$location->location [$location->lat, $location->lng]", true);
77        $this->assertEquals("$location->location [$location->lat, $location->lng]", $device->location->location);
78        $this->assertEquals($location->location, $device->location->display());
79        $this->assertEquals("$location->location [$location->lat,$location->lng]", $device->location->display(true));
80        $this->assertEquals($location->lat, $device->location->lat);
81        $this->assertEquals($location->lng, $device->location->lng);
82
83        // invalid coords
84        $location = Location::factory()->withCoordinates()->make(['lat' => 251.5007138]);
85        $name = "$location->location [$location->lat,$location->lng]";
86        $device->setLocation($name, true);
87        $this->assertEquals($name, $device->location->location);
88        $this->assertEquals($name, $device->location->display());
89        $this->assertEquals($name, $device->location->display(true));
90        $this->assertNull($device->location->lat);
91        $this->assertNull($device->location->lng);
92    }
93
94    public function testCanHandleGivenCoordinates()
95    {
96        Config::set('geoloc.dns', false);
97        $device = Device::factory()->make(); /** @var Device $device */
98        $location = Location::factory()->withCoordinates()->make();
99
100        $device->setLocation($location);
101        $this->assertEquals($location->location, $device->location->location);
102        $this->assertEquals($location->location, $device->location->display());
103        $this->assertEquals("$location->location [$location->lat,$location->lng]", $device->location->display(true));
104        $this->assertEquals($location->lat, $device->location->lat);
105        $this->assertEquals($location->lng, $device->location->lng);
106    }
107
108    public function testCanNotSetFixedCoordinates()
109    {
110        $device = Device::factory()->make(); /** @var Device $device */
111        $locationOne = Location::factory()->withCoordinates()->make();
112        $locationTwo = Location::factory(['location' => $locationOne->location])->withCoordinates()->make();
113
114        $device->setLocation($locationOne);
115        $this->assertEquals($locationOne->lat, $device->location->lat);
116        $this->assertEquals($locationOne->lng, $device->location->lng);
117
118        $device->location->fixed_coordinates = true;
119        $device->setLocation($locationTwo);
120        $this->assertEquals($locationOne->lat, $device->location->lat);
121        $this->assertEquals($locationOne->lng, $device->location->lng);
122
123        $device->location->fixed_coordinates = false;
124        $device->setLocation($locationTwo);
125        $this->assertEquals($locationTwo->lat, $device->location->lat);
126        $this->assertEquals($locationTwo->lng, $device->location->lng);
127    }
128
129    public function testDnsLookup()
130    {
131        $example = 'SW1A2AA.find.me.uk';
132        $expected = ['lat' => 51.50354111111111, 'lng' => -0.12766972222222223];
133
134        $result = (new Dns())->getCoordinates($example);
135
136        $this->assertEquals($expected, $result);
137    }
138
139    public function testCanSetDnsCoordinate()
140    {
141        Config::set('geoloc.dns', true);
142        $device = Device::factory()->make(); /** @var Device $device */
143        $location = Location::factory()->withCoordinates()->make();
144
145        $this->mock(Dns::class, function (MockInterface $mock) use ($location) {
146            $mock->shouldReceive('getCoordinates')->once()->andReturn($location->only(['lat', 'lng']));
147        });
148
149        $device->setLocation($location->location, true);
150        $this->assertEquals($location->location, $device->location->location);
151        $this->assertEquals($location->lat, $device->location->lat);
152        $this->assertEquals($location->lng, $device->location->lng);
153
154        Config::set('geoloc.dns', false);
155        $device->setLocation('No DNS', true);
156        $this->assertEquals('No DNS', $device->location->location);
157        $this->assertNull($device->location->lat);
158        $this->assertNull($device->location->lng);
159    }
160
161    public function testCanSetByApi()
162    {
163        $device = Device::factory()->make(); /** @var Device $device */
164        $location = Location::factory()->withCoordinates()->make();
165
166        $this->mock(Geocoder::class, function (MockInterface $mock) use ($location) {
167            $mock->shouldReceive('getCoordinates')->once()->andReturn($location->only(['lat', 'lng']));
168        });
169
170        Config::set('geoloc.latlng', false);
171        $device->setLocation('No API', true);
172        $this->assertEquals('No API', $device->location->location);
173        $this->assertNull($device->location->lat);
174        $this->assertNull($device->location->lng);
175
176        Config::set('geoloc.latlng', true);
177        $device->setLocation('API', true);
178        $this->assertEquals('API', $device->location->location);
179        $this->assertEquals($location->lat, $device->location->lat);
180        $this->assertEquals($location->lng, $device->location->lng);
181
182        // preset coord = skip api
183        $device->setLocation('API', true);
184        $this->assertEquals($location->lat, $device->location->lat);
185        $this->assertEquals($location->lng, $device->location->lng);
186    }
187
188    public function testCorrectPrecedence()
189    {
190        $device = Device::factory()->make(); /** @var Device $device */
191        $location_encoded = Location::factory()->withCoordinates()->make();
192        $location_fixed = Location::factory()->withCoordinates()->make();
193        $location_api = Location::factory()->withCoordinates()->make();
194        $location_dns = Location::factory()->withCoordinates()->make();
195
196        Config::set('geoloc.dns', true);
197        $this->mock(Dns::class, function (MockInterface $mock) use ($location_dns) {
198            $mock->shouldReceive('getCoordinates')->times(3)->andReturn(
199                $location_dns->only(['lat', 'lng']),
200                [],
201                []
202            );
203        });
204
205        Config::set('geoloc.latlng', true);
206        $this->mock(Geocoder::class, function (MockInterface $mock) use ($location_api) {
207            $mock->shouldReceive('getCoordinates')->once()->andReturn($location_api->only(['lat', 'lng']));
208        });
209
210        // fixed first
211        $location_fixed->location = "$location_fixed [-42, 42]"; // encoded should not be used
212        $device->setLocation($location_fixed, true);
213        $this->assertEquals($location_fixed->lat, $device->location->lat);
214        $this->assertEquals($location_fixed->lng, $device->location->lng);
215
216        // then encoded
217        $device->setLocation($location_encoded->display(true), true);
218        $this->assertEquals($location_encoded->lat, $device->location->lat);
219        $this->assertEquals($location_encoded->lng, $device->location->lng);
220
221        // then dns
222        $device->setLocation($location_encoded->location, true);
223        $this->assertEquals($location_dns->lat, $device->location->lat);
224        $this->assertEquals($location_dns->lng, $device->location->lng);
225
226        // then api
227        $device->setLocation($location_encoded->location, true);
228        $this->assertEquals($location_dns->lat, $device->location->lat);
229        $this->assertEquals($location_dns->lng, $device->location->lng);
230
231        $device->location->lat = null; // won't be used if latitude is set
232        $device->setLocation($location_encoded->location, true);
233        $this->assertEquals($location_api->lat, $device->location->lat);
234        $this->assertEquals($location_api->lng, $device->location->lng);
235    }
236}
237