Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ RELEASING:
14. Create new release in GitHub with tag version and release title of `vX.X.X`
-->

## Unreleased
### Added
- API key validation for selected provider ([#339](https://github.com/GIScience/orstools-qgis-plugin/issues/339))

## [2.0.1] - 2025-06-01
### Added
- Readd old icons and add new icons for processing algorithms
Expand Down
55 changes: 52 additions & 3 deletions ORStools/gui/ORStoolsDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from datetime import datetime
from typing import Optional

from qgis.PyQt.QtWidgets import QCheckBox
from qgis.PyQt.QtNetwork import QNetworkRequest

from ..utils.router import route_as_layer

Expand All @@ -57,11 +57,20 @@
Qgis, # noqa: F811
QgsAnnotation,
QgsCoordinateTransform,
QgsBlockingNetworkRequest,
)
from qgis.gui import QgsMapCanvasAnnotationItem, QgsCollapsibleGroupBox, QgisInterface
from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication
from qgis.PyQt.QtCore import QSizeF, QPointF, QCoreApplication, QUrl
from qgis.PyQt.QtGui import QTextDocument
from qgis.PyQt.QtWidgets import QAction, QDialog, QApplication, QMenu, QMessageBox, QDialogButtonBox
from qgis.PyQt.QtWidgets import (
QAction,
QDialog,
QApplication,
QMenu,
QMessageBox,
QDialogButtonBox,
QCheckBox,
)
from qgis.PyQt.QtGui import QColor
from qgis.PyQt.QtWidgets import (
QWidget,
Expand Down Expand Up @@ -385,6 +394,7 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:
self.provider_config.setIcon(gui.GuiUtils.get_icon("icon_settings.png"))
self.about_button.setIcon(gui.GuiUtils.get_icon("icon_about.png"))
self.help_button.setIcon(gui.GuiUtils.get_icon("icon_help.png"))
self.check_key_button.setIcon(gui.GuiUtils.get_icon("icon_key.png"))

# Connect signals to the color_duplicate_items function
self.routing_fromline_list.model().rowsRemoved.connect(
Expand All @@ -396,6 +406,8 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:

self.load_provider_combo_state()
self.provider_combo.activated.connect(self.save_selected_provider_state)
self.provider_combo.currentIndexChanged.connect(self.set_check_provider_button)
self.check_key_button.clicked.connect(self.check_api_key)

advanced_boxes = self.advances_group.findChildren(QgsCollapsibleGroupBox)
for box in advanced_boxes:
Expand All @@ -408,6 +420,43 @@ def __init__(self, iface: QgisInterface, parent=None) -> None:

self.rubber_band = None

def set_check_provider_button(self):
"""Checks, whether the current provider is of the public API and set a check provider if so"""
provider = configmanager.read_config()["providers"][self.provider_combo.currentIndex()]
url = provider.get("base_url", "")
if url == "https://api.openrouteservice.org":
self.check_key_button.show()
else:
self.check_key_button.hide()

def check_api_key(self) -> None:
provider = configmanager.read_config()["providers"][self.provider_combo.currentIndex()]
api_key = provider.get("key", "")

if not api_key:
QMessageBox.critical(
self, self.tr("Error"), self.tr("No API key set for the selected provider.")
)
return

url = f"https://api.openrouteservice.org/v2/directions/driving-car?api_key={api_key}&start=8.681495,49.41461&end=8.687872,49.420318"
request = QgsBlockingNetworkRequest()
qrequest = QNetworkRequest(QUrl(url))
qrequest.setRawHeader(
b"Accept",
b"application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8",
)
error_code = request.get(qrequest)

if error_code == QgsBlockingNetworkRequest.ErrorCode.NoError:
QMessageBox.information(self, self.tr("Success"), self.tr("API key check successful!"))
else:
QMessageBox.critical(
self,
self.tr("API key Error"),
self.tr(f"API key check failed, key: {api_key} is invalid."),
)

def _save_vertices_to_layer(self) -> None:
"""Saves the vertices list to a temp layer"""
items = [
Expand Down
7 changes: 7 additions & 0 deletions ORStools/gui/ORStoolsDialogUI.ui
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="check_key_button">
<property name="toolTip">
<string>Check if the API key for the selected provider is valid</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
Binary file added ORStools/gui/img/icon_key.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 51 additions & 1 deletion tests/test_gui.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from unittest.mock import patch

from qgis.PyQt.QtWidgets import QLineEdit
from qgis.core import QgsSettings
from qgis.gui import QgsCollapsibleGroupBox
from qgis.testing import unittest

from qgis.PyQt.QtTest import QTest
from qgis.PyQt.QtCore import Qt, QEvent, QPoint
from qgis.PyQt.QtWidgets import QPushButton
from qgis.PyQt.QtWidgets import QPushButton, QMessageBox
from qgis.gui import QgsMapCanvas, QgsMapMouseEvent, QgsRubberBand
from qgis.core import (
QgsCoordinateReferenceSystem,
Expand Down Expand Up @@ -358,3 +360,51 @@ def test_ORStoolsDialogConfig_url(self):
"POINT(8.67251100000000008 49.39887900000000087)",
next(layer.getFeatures()).geometry().asPolyline()[0].asWkt(),
)

def test_check_key_button(self):
from ORStools.gui.ORStoolsDialog import ORStoolsDialog
from ORStools.gui.ORStoolsDialogConfig import ORStoolsDialogConfigMain

CRS = QgsCoordinateReferenceSystem.fromEpsgId(3857)
CANVAS.setExtent(QgsRectangle(258889, 7430342, 509995, 7661955))
CANVAS.setDestinationCrs(CRS)
CANVAS.setFrameStyle(0)
CANVAS.resize(600, 400)
self.assertEqual(CANVAS.width(), 600)
self.assertEqual(CANVAS.height(), 400)

dlg = ORStoolsDialog(IFACE)
dlg.open()
self.assertTrue(dlg.isVisible())

# Set and reset config to test whether the reset works
dlg_config = ORStoolsDialogConfigMain()
provider = dlg_config.providers.findChildren(QgsCollapsibleGroupBox)[0]

# set endpoint of directions to non-existent value
line_edit = provider.findChild(QLineEdit, "openrouteservice_key_text")
api_key = line_edit.text()
line_edit.setText("notanapikey")
dlg_config.accept()

settings_directions_endpoint = QgsSettings().value("ORStools/config")["providers"][0][
"key"
]

self.assertEqual(settings_directions_endpoint, "notanapikey")

with patch.object(QMessageBox, "information", return_value=QMessageBox.Ok) as mock_info, \
patch.object(QMessageBox, "critical", return_value=QMessageBox.Ok) as mock_crit:
dlg.check_key_button.clicked.emit()

dlg_config = ORStoolsDialogConfigMain()
provider = dlg_config.providers.findChildren(QgsCollapsibleGroupBox)[0]

line_edit = provider.findChild(QLineEdit, "openrouteservice_key_text")
line_edit.setText(api_key)
dlg_config.accept()

dlg.check_key_button.clicked.emit()

mock_info.assert_called_once()
mock_crit.assert_called_once()