Skip to content

Commit

Permalink
flake and testing
Browse files Browse the repository at this point in the history
  • Loading branch information
benvanbasten-ns committed Nov 29, 2024
1 parent fd5077d commit f7fd21e
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 57 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
- name: build
run: docker compose build

- name: test
run: |
docker compose run --rm qgis-desktop flake8 .
- name: test
run: |
docker compose run --rm qgis-desktop pytest
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ mock
pip-tools
pytest
pytest-qt
flake8
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[flake8]
exclude=.venv, tests
max-line-length = 120
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
url="https://github.com/nens/threedi-mi-utils",
license="GNU General Public License v3.0",
author="Łukasz Dębek",
description="Python package with tools for managing the working 3Di directory structure",
description="Python package with utilities for the 3Di Modeller Interface",
long_description=long_description,
long_description_content_type="text/markdown",
)
50 changes: 41 additions & 9 deletions threedi_mi_utils/news/news_injector.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
from pathlib import Path
from typing import List

Expand All @@ -6,27 +7,58 @@

__all__ = ["NewsInjector"]

# We still need this url as the entry in the setting is based on this, and (hardcoded) used for later lookup
# We still need this url as the entry in the setting is based on this,
# and (hardcoded) used for later lookup
feed_url = "https://feed.qgis.org/"
# To distinguish custom news items from QGIS news items,
# we start custom items at this (extremely high) offset
pk_offset = 10000000

class NewsInjector:

class NewsInjector:
def __init__(self):
self.refresh()
self.update()

def load(self, file_path: Path) -> bool:
"""Loads all news items from a provided JSON file"""
with open(file_path, 'r') as file:
self.update()
existing_entries = self.parser.entries()

# Check whether all items > pk_offset and don't already exists
entries = json.load(file)
filtered_entries = []
for entry in entries:
pk = entry["pk"]
if pk < pk_offset:
continue

if next((x for x in existing_entries if x.key == pk), None):
continue

filtered_entries.append(entry)

self.add_items(json.dumps(filtered_entries))
self.update()

return True

def refresh(self) -> None:
"""Reads all the entries from the settings and prunes expired entries"""
# This invokes readStoredEntries() which also prunes expired entries
def update(self) -> None:
"""Reads all the entries from the settings and prunes expired entries,
including entries with the same id"""
# invokes readStoredEntries()
self.parser = QgsNewsFeedParser(QUrl(feed_url))

def items(self) -> List[QgsNewsFeedParser.Entry]:
"""Returns the list of news items"""
return self.parser.entries()

def clear(self) -> None:
"""Removes all news items, including QGIS items, useful for testing"""
self.parser.dismissAll()

def add_items(self, items_string: str) -> None:
"""Add items"""

# Stores entries in member variable AND settings
"""Add items, if there are expired items, these are only removed after
a call to update()"""
# This stores entries in member variable AND settings
self.parser.onFetch(items_string)
5 changes: 3 additions & 2 deletions threedi_mi_utils/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
_singletons = {}
logger = logging.getLogger(__name__)


@pytest.fixture(autouse=True)
def qgis_app_initialized():
"""Make sure qgis is initialized for testing."""
if "app" not in _singletons:
app = QgsApplication([], False)
app.initQgis()
logger.debug("Initialized qgis (for testing). Settings: %s", app.showSettings())
_singletons["app"] = app
logger.debug("Initialized qgis (for testing): %s", app.showSettings())
_singletons["app"] = app
31 changes: 0 additions & 31 deletions threedi_mi_utils/tests/data/feed.json

This file was deleted.

32 changes: 32 additions & 0 deletions threedi_mi_utils/tests/data/feed_double.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[{
"pk": 10000004,
"publish_from": 1557073748.13,
"publish_to": null,
"title": "title1",
"image": "",
"content": "<p>content1</p>",
"url": "link1",
"sticky": true
},
{
"pk": 10000005,
"publish_from": 915196568.0,
"publish_to": 32472144000.0,
"title": "title2",
"image": "image1",
"content": "<p>content2</p>",
"url": "link2",
"sticky": true
},
{
"pk": 10000005,
"publish_from": 1557360008.132,
"publish_to": null,
"title": "title3",
"image": "",
"content": "<p>content3</p>",
"url": "link3",
"sticky": false
}

]
42 changes: 42 additions & 0 deletions threedi_mi_utils/tests/data/feed_expired.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[{
"pk": 10000004,
"publish_from": 1557073748.13,
"publish_to": null,
"title": "title1",
"image": "",
"content": "<p>content1</p>",
"url": "link1",
"sticky": true
},
{
"pk": 10000005,
"publish_from": 915196568.0,
"publish_to": 32472144000.0,
"title": "title2",
"image": "image1",
"content": "<p>content2</p>",
"url": "link2",
"sticky": true
},
{
"pk": 10000006,
"publish_from": 1557360008.132,
"publish_to": null,
"title": "title3",
"image": "",
"content": "<p>content3</p>",
"url": "link3",
"sticky": false
},
{
"pk": 10000007,
"publish_from": null,
"publish_to": 57360008.132,
"title": "title3",
"image": "",
"content": "<p>expired</p>",
"url": "link3",
"sticky": false
}

]
21 changes: 21 additions & 0 deletions threedi_mi_utils/tests/data/feed_too_small_pk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[{
"pk": 4,
"publish_from": 1557073748.13,
"publish_to": null,
"title": "title1",
"image": "",
"content": "<p>content1</p>",
"url": "link1",
"sticky": true
},
{
"pk": 10000005,
"publish_from": 915196568.0,
"publish_to": 32472144000.0,
"title": "title2",
"image": "image1",
"content": "<p>content2</p>",
"url": "link2",
"sticky": true
}
]
63 changes: 49 additions & 14 deletions threedi_mi_utils/tests/news_test.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
from pathlib import Path
from typing import Any

import pytest

from threedi_mi_utils.news import NewsInjector


@pytest.fixture()
def news_injector():
injector = NewsInjector()
yield injector
injector.clear()

def none_to_null(value: Any) -> str:
return value if value is not None else "null"


def compare(entry, test_item):
assert entry.key == test_item.pk
assert entry.expiry.toSecsSinceEpoch() if entry.expiry.isValid() else None == test_item.publish_to
assert ((entry.expiry.toSecsSinceEpoch() if entry.expiry.isValid() else None)
== int(test_item.publish_to))
assert entry.title == test_item.title
assert entry.content == test_item.content
assert entry.imageUrl == test_item.image
assert entry.link.toString() == test_item.url
assert entry.sticky == test_item.sticky



class TestItem():

__test__ = False
def __init__(self, pk: int, publish_from: int, publish_to: int, title: str, image: str, content: str, url: str, sticky: bool):

def __init__(self, pk: int, publish_from: int, publish_to: int, title: str,
image: str, content: str, url: str, sticky: bool):
self.pk = pk
self.publish_from = publish_from
self.publish_to = publish_to
Expand All @@ -34,11 +35,17 @@ def __init__(self, pk: int, publish_from: int, publish_to: int, title: str, imag
self.content = content
self.url = url
self.sticky = sticky

def __repr__(self):
return f'[{{"pk": {none_to_null(self.pk)}, "publish_from": {none_to_null(self.publish_from)}, "publish_to": {none_to_null(self.publish_to)}, \
"title": "{none_to_null(self.title)}", "image": "{none_to_null(self.image)}", "content": "{none_to_null(self.content)}", "url": "{none_to_null(self.url)}", \
"sticky": {none_to_null(str(self.sticky).lower())}}}]'

@pytest.fixture()
def news_injector():
injector = NewsInjector()
yield injector
injector.clear()

@pytest.fixture()
def news_item():
Expand All @@ -53,21 +60,25 @@ def sticky_news_item(news_item):
def expired_news_item():
return TestItem(pk=5, publish_from=None, publish_to=17073748.13, title="test title", image="", content="<p>test content</p>", url="test", sticky=False)

@pytest.fixture()
def data_folder():
return Path(__file__).parent / "data"

def test_no_news_at_start(news_injector):
assert len(news_injector.items()) == 0

def test_add_item(news_injector, news_item):
news_injector.add_items(str(news_item))
assert len(news_injector.items()) == 1
news_injector.refresh()
news_injector.update()
assert len(news_injector.items()) == 1
entry = news_injector.items()[0]
compare(entry, news_item)

def test_add_sticky_item(news_injector, sticky_news_item):
news_injector.add_items(str(sticky_news_item))
assert len(news_injector.items()) == 1
news_injector.refresh()
news_injector.update()
assert len(news_injector.items()) == 1
entry = news_injector.items()[0]
assert entry.sticky == True
Expand All @@ -79,7 +90,7 @@ def test_add_expired_item(news_injector, expired_news_item):
assert len(news_injector.items()) == 1
entry = news_injector.items()[0]
compare(entry, expired_news_item)
news_injector.refresh() # ... but are pruned at a refresh()
news_injector.update() # ... but are pruned at an update()
assert len(news_injector.items()) == 0

def test_add_corrupt_item(news_injector):
Expand All @@ -91,5 +102,29 @@ def test_item_twice(news_injector, news_item):
assert len(news_injector.items()) == 1
news_injector.add_items(str(news_item))
assert len(news_injector.items()) == 2
news_injector.refresh() # Double items are pruned at a refresh
news_injector.update() # Double items are pruned at an update
assert len(news_injector.items()) == 1

def test_load_items_expired(news_injector, data_folder):
assert len(news_injector.items()) == 0
assert news_injector.load(data_folder / "feed_expired.json")
# one entry is expired, so 3 items
assert len(news_injector.items()) == 3
news_injector.items()[0].key == 10000004
news_injector.items()[1].key == 10000005
news_injector.items()[1].key == 10000006

def test_load_items_double(news_injector, data_folder):
assert news_injector.load(data_folder / "feed_double.json")
# double ids, so 2 items
assert len(news_injector.items()) == 2
news_injector.items()[0].key == 10000004
news_injector.items()[1].key == 10000005
news_injector.items()[1].title == "title3" # last double item remains
news_injector.items()[1].link == "link3" # last double item remains

def test_load_items_too_small_pk(news_injector, data_folder):
assert news_injector.load(data_folder / "feed_too_small_pk.json")
# double ids, so 2 items
assert len(news_injector.items()) == 1
news_injector.items()[0].key == 10000005

0 comments on commit f7fd21e

Please sign in to comment.