Skip to content

Commit b5b9304

Browse files
large change to test structure and coverage. These tests are mostly AI generated and will be manually reviewed as time permits
1 parent 5bd72d7 commit b5b9304

13 files changed

Lines changed: 778 additions & 1038 deletions

.github/workflows/tests.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Tests
2+
on: [ push, pull_request, workflow_dispatch ]
3+
4+
permissions: {}
5+
6+
concurrency:
7+
group: tests-${{ github.ref }}
8+
cancel-in-progress: true
9+
10+
jobs:
11+
pytest:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
python-version: [ "3.12", "3.13", "3.14" ]
19+
name: pytest (Python ${{ matrix.python-version }})
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v5
23+
24+
- name: Install uv
25+
uses: astral-sh/setup-uv@v6
26+
27+
- name: Install Python ${{ matrix.python-version }}
28+
run: uv python install ${{ matrix.python-version }}
29+
30+
- name: Install dependencies
31+
run: uv sync --all-extras --python ${{ matrix.python-version }}
32+
33+
# Network-dependent tests need a live OSH server (e.g. localhost:8282).
34+
# They're tagged `@pytest.mark.network` and skipped here. The plan is
35+
# to shim those with mocks; once a test no longer needs a real server,
36+
# drop the marker and it will run in CI automatically.
37+
- name: Run pytest
38+
run: uv run --python ${{ matrix.python-version }} pytest -v -m "not network"

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "oshconnect"
3-
version = "0.5.0a0"
3+
version = "0.5.0a1"
44
description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations. Implements OGC CS API Part 3 (Pub/Sub) MQTT topic conventions including :data topics and resource event topics."
55
readme = "README.md"
66
authors = [
@@ -31,3 +31,6 @@ packages = {find = { where = ["src/"]}}
3131

3232
[tool.pytest.ini_options]
3333
pythonpath = ["src"]
34+
markers = [
35+
"network: test requires a live OSH server or external network endpoint (skipped by default in CI; see workflow `tests.yaml`).",
36+
]

tests/test_api_helper.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

tests/test_imports.py

Lines changed: 55 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,55 @@
1-
# =============================================================================
2-
# Copyright (c) 2025 Botts Innovative Research Inc.
3-
# Date: 2025/4/2
4-
# Author: Ian Patterson
5-
# Contact Email: ian@botts-inc.com
6-
# =============================================================================
7-
#
8-
# Verifies that all public symbols are importable from the top-level package
9-
# and from the csapi4py subpackage. Run with:
10-
# uv run pytest tests/test_imports.py
11-
#
12-
# Requirements: the package must be installed in the environment first:
13-
# uv sync (or) pip install -e .
14-
# =============================================================================
15-
16-
17-
# ---------------------------------------------------------------------------
18-
# Top-level package
19-
# ---------------------------------------------------------------------------
20-
21-
def test_core_resources_importable():
22-
from oshconnect import OSHConnect, Node, System, Datastream, ControlStream
23-
assert OSHConnect is not None
24-
assert Node is not None
25-
assert System is not None
26-
assert Datastream is not None
27-
assert ControlStream is not None
28-
29-
30-
def test_streaming_enums_importable():
31-
from oshconnect import StreamableModes, Status
32-
assert StreamableModes is not None
33-
assert Status is not None
34-
35-
36-
def test_time_management_importable():
37-
from oshconnect import TimePeriod, TimeInstant, TemporalModes, TimeUtils
38-
assert TimePeriod is not None
39-
assert TimeInstant is not None
40-
assert TemporalModes is not None
41-
assert TimeUtils is not None
42-
43-
44-
def test_resource_datamodels_importable():
45-
from oshconnect import (
46-
SystemResource,
47-
DatastreamResource,
48-
ControlStreamResource,
49-
ObservationResource,
50-
)
51-
assert SystemResource is not None
52-
assert DatastreamResource is not None
53-
assert ControlStreamResource is not None
54-
assert ObservationResource is not None
55-
56-
57-
def test_swe_schema_components_importable():
58-
from oshconnect import (
59-
DataRecordSchema,
60-
VectorSchema,
61-
QuantitySchema,
62-
TimeSchema,
63-
BooleanSchema,
64-
CountSchema,
65-
CategorySchema,
66-
TextSchema,
67-
QuantityRangeSchema,
68-
TimeRangeSchema,
69-
)
70-
for cls in (DataRecordSchema, VectorSchema, QuantitySchema, TimeSchema,
71-
BooleanSchema, CountSchema, CategorySchema, TextSchema,
72-
QuantityRangeSchema, TimeRangeSchema):
73-
assert cls is not None
74-
75-
76-
def test_schema_datamodels_importable():
77-
from oshconnect import SWEDatastreamRecordSchema, JSONCommandSchema
78-
assert SWEDatastreamRecordSchema is not None
79-
assert JSONCommandSchema is not None
80-
81-
82-
def test_event_system_importable():
83-
from oshconnect import (
84-
EventHandler,
85-
IEventListener,
86-
DefaultEventTypes,
87-
AtomicEventTypes,
88-
Event,
89-
EventBuilder,
90-
)
91-
assert EventHandler is not None
92-
assert IEventListener is not None
93-
assert DefaultEventTypes is not None
94-
assert AtomicEventTypes is not None
95-
assert Event is not None
96-
assert EventBuilder is not None
97-
98-
99-
def test_csapi_constants_importable():
100-
from oshconnect import ObservationFormat, APIResourceTypes, ContentTypes
101-
assert ObservationFormat is not None
102-
assert APIResourceTypes is not None
103-
assert ContentTypes is not None
104-
105-
106-
def test_all_list_present_and_complete():
107-
import oshconnect
108-
assert hasattr(oshconnect, "__all__")
109-
assert len(oshconnect.__all__) > 0
110-
for name in oshconnect.__all__:
111-
assert hasattr(oshconnect, name), f"__all__ lists '{name}' but it is not importable"
112-
113-
114-
# ---------------------------------------------------------------------------
115-
# csapi4py subpackage
116-
# ---------------------------------------------------------------------------
117-
118-
def test_csapi4py_constants_importable():
119-
from oshconnect.csapi4py import APIResourceTypes, ObservationFormat, ContentTypes, APITerms, SystemTypes
120-
assert APIResourceTypes is not None
121-
assert ObservationFormat is not None
122-
assert ContentTypes is not None
123-
assert APITerms is not None
124-
assert SystemTypes is not None
125-
126-
127-
def test_csapi4py_request_builder_importable():
128-
from oshconnect.csapi4py import ConnectedSystemsRequestBuilder, ConnectedSystemAPIRequest
129-
assert ConnectedSystemsRequestBuilder is not None
130-
assert ConnectedSystemAPIRequest is not None
131-
132-
133-
def test_csapi4py_mqtt_importable():
134-
from oshconnect.csapi4py import MQTTCommClient
135-
assert MQTTCommClient is not None
136-
137-
138-
def test_csapi4py_api_helper_importable():
139-
from oshconnect.csapi4py import APIHelper
140-
assert APIHelper is not None
141-
142-
143-
def test_csapi4py_all_list_present_and_complete():
144-
import oshconnect.csapi4py as csapi4py
145-
assert hasattr(csapi4py, "__all__")
146-
for name in csapi4py.__all__:
147-
assert hasattr(csapi4py, name), f"__all__ lists '{name}' but it is not importable"
1+
"""Public-API smoke tests: every name in `oshconnect.__all__` and
2+
`oshconnect.csapi4py.__all__` is importable from its package, and the
3+
re-exports we document users relying on actually resolve.
4+
"""
5+
import importlib
6+
7+
import pytest
8+
9+
# (package, names) — the documented public surface, grouped by concern.
10+
EXPECTED_REEXPORTS = [
11+
("oshconnect", ["OSHConnect", "Node", "System", "Datastream", "ControlStream"]),
12+
("oshconnect", ["StreamableModes", "Status"]),
13+
("oshconnect", ["TimePeriod", "TimeInstant", "TemporalModes", "TimeUtils"]),
14+
("oshconnect", ["SystemResource", "DatastreamResource", "ControlStreamResource",
15+
"ObservationResource"]),
16+
("oshconnect", ["DataRecordSchema", "VectorSchema", "QuantitySchema",
17+
"TimeSchema", "BooleanSchema", "CountSchema", "CategorySchema",
18+
"TextSchema", "QuantityRangeSchema", "TimeRangeSchema"]),
19+
("oshconnect", ["SWEDatastreamRecordSchema", "JSONCommandSchema"]),
20+
("oshconnect", ["EventHandler", "IEventListener", "DefaultEventTypes",
21+
"AtomicEventTypes", "Event", "EventBuilder"]),
22+
("oshconnect", ["ObservationFormat", "APIResourceTypes", "ContentTypes"]),
23+
("oshconnect.csapi4py", ["APIResourceTypes", "ObservationFormat", "ContentTypes",
24+
"APITerms", "SystemTypes"]),
25+
("oshconnect.csapi4py", ["ConnectedSystemsRequestBuilder",
26+
"ConnectedSystemAPIRequest"]),
27+
("oshconnect.csapi4py", ["MQTTCommClient"]),
28+
("oshconnect.csapi4py", ["APIHelper"]),
29+
]
30+
31+
32+
@pytest.mark.parametrize(
33+
"package,names",
34+
EXPECTED_REEXPORTS,
35+
ids=[f"{pkg}:{','.join(names[:2])}{'…' if len(names) > 2 else ''}"
36+
for pkg, names in EXPECTED_REEXPORTS],
37+
)
38+
def test_documented_reexports_resolve(package, names):
39+
mod = importlib.import_module(package)
40+
for name in names:
41+
assert hasattr(mod, name), (
42+
f"{package} is expected to re-export {name!r} but does not"
43+
)
44+
assert getattr(mod, name) is not None
45+
46+
47+
@pytest.mark.parametrize("package", ["oshconnect", "oshconnect.csapi4py"])
48+
def test_all_list_present_and_complete(package):
49+
mod = importlib.import_module(package)
50+
assert hasattr(mod, "__all__"), f"{package} has no __all__"
51+
assert len(mod.__all__) > 0, f"{package}.__all__ is empty"
52+
for name in mod.__all__:
53+
assert hasattr(mod, name), (
54+
f"{package}.__all__ lists {name!r} but it is not importable"
55+
)

tests/test_node.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Node and APIHelper basics: URL construction and (de)serialization."""
2+
from oshconnect import Node
3+
from oshconnect.csapi4py import APIHelper
4+
5+
6+
def test_apihelper_url_generation():
7+
helper = APIHelper(server_url='localhost', port=8282, protocol='http',
8+
username='admin', password='admin')
9+
10+
assert helper.get_api_root_url() == "http://localhost:8282/sensorhub/api"
11+
assert helper.get_api_root_url(socket=True) == "ws://localhost:8282/sensorhub/api"
12+
13+
helper.set_protocol('https')
14+
assert helper.get_api_root_url() == "https://localhost:8282/sensorhub/api"
15+
assert helper.get_api_root_url(socket=True) == "wss://localhost:8282/sensorhub/api"
16+
17+
18+
def test_node_password_round_trips_through_serialization():
19+
node = Node(protocol='http', address='localhost', port=8080,
20+
username='user', password='pass')
21+
serialized = node.serialize()
22+
assert serialized['password'] == 'pass'
23+
deserialized = Node.deserialize(serialized)
24+
assert deserialized._api_helper.password == 'pass'

0 commit comments

Comments
 (0)