Updated Owncast clients to v0.2.5 and added spec-coverage and integration tests to enforce parity with the OpenAPI spec.
CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 2m55s
CI / Tests (Python 3.13) (push) Successful in 2m46s
CI / Tests (Python 3.14) (push) Successful in 2m42s
CI / Type Checking (push) Successful in 10s
CI / Spelling (push) Successful in 9s
CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 2m55s
CI / Tests (Python 3.13) (push) Successful in 2m46s
CI / Tests (Python 3.14) (push) Successful in 2m42s
CI / Type Checking (push) Successful in 10s
CI / Spelling (push) Successful in 9s
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Copyright 2026 Logan Fick
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Integration tests for ``OwncastClient`` against a live Owncast server."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from owlbot.owncast_http import OwncastError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from owlbot.api.owncast_client import OwncastClient
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
def test_base_url_matches_server(
|
||||
owncast_client: OwncastClient, owncast_server: str
|
||||
) -> None:
|
||||
"""The client's base_url property reflects the server it was built for."""
|
||||
assert owncast_client.base_url == owncast_server
|
||||
|
||||
|
||||
class TestRoundTrip:
|
||||
"""End-to-end tests whose effect is verified via readback or return value.
|
||||
|
||||
Either the call is a getter whose return value is the observation, or the
|
||||
call is a setter/mutation whose effect is read back within the same test.
|
||||
"""
|
||||
|
||||
async def test_send_message_returns_success_envelope(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_message posts a chat message and returns the success envelope."""
|
||||
result = await owncast_client.send_message("integration test message")
|
||||
assert isinstance(result, str)
|
||||
|
||||
async def test_send_message_with_unsanitized_true(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_message accepts raw HTML when unsanitized=True."""
|
||||
result = await owncast_client.send_message("<b>bold</b>", unsanitized=True)
|
||||
assert isinstance(result, str)
|
||||
|
||||
async def test_send_system_message_returns_success_envelope(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_system_message posts a system message."""
|
||||
result = await owncast_client.send_system_message("system notice")
|
||||
assert isinstance(result, str)
|
||||
|
||||
async def test_send_action_returns_success_envelope(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_action posts an action message."""
|
||||
result = await owncast_client.send_action("does a test thing")
|
||||
assert isinstance(result, str)
|
||||
|
||||
async def test_send_user_message_always_returns_400(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_user_message is deprecated in Owncast v0.2.5 and returns 400.
|
||||
|
||||
Validates the deprecation contract documented on
|
||||
OwncastClient.send_user_message.
|
||||
"""
|
||||
with pytest.raises(OwncastError) as exc_info:
|
||||
await owncast_client.send_user_message()
|
||||
assert exc_info.value.status == 400
|
||||
|
||||
async def test_get_chat_history_is_empty_on_fresh_server(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""get_chat_history returns [] on a fresh server with no sent messages."""
|
||||
assert await owncast_client.get_chat_history() == []
|
||||
|
||||
async def test_get_connected_clients_is_empty_on_fresh_server(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""get_connected_clients returns [] on a fresh server with no viewers."""
|
||||
assert await owncast_client.get_connected_clients() == []
|
||||
|
||||
async def test_set_message_visibility_after_sending(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""set_message_visibility accepts message IDs read from history.
|
||||
|
||||
Sends a message, reads chat history to obtain its id, hides it, then
|
||||
shows it again. Validates the full write-read-mutate round trip.
|
||||
"""
|
||||
await owncast_client.send_message("visibility-test message")
|
||||
history = await owncast_client.get_chat_history()
|
||||
assert history, "chat history should contain the just-sent message"
|
||||
message_id = str(history[-1]["id"])
|
||||
hide_result = await owncast_client.set_message_visibility(
|
||||
[message_id], visible=False
|
||||
)
|
||||
assert isinstance(hide_result, str)
|
||||
show_result = await owncast_client.set_message_visibility(
|
||||
[message_id], visible=True
|
||||
)
|
||||
assert isinstance(show_result, str)
|
||||
|
||||
async def test_get_user_details_for_existing_user(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""get_user_details returns a dict for a user extracted from chat history.
|
||||
|
||||
The integration bot sends a message, then reads chat history to get
|
||||
its own user id, and looks it up. This is the only way to test the
|
||||
endpoint end-to-end since Owncast returns 404 for unknown user ids
|
||||
(so we can't distinguish "route missing" from "user missing" without
|
||||
a real id).
|
||||
"""
|
||||
await owncast_client.send_message("user-details-test message")
|
||||
history = await owncast_client.get_chat_history()
|
||||
assert history
|
||||
user_id = str(history[-1]["user"]["id"])
|
||||
details = await owncast_client.get_user_details(user_id)
|
||||
assert isinstance(details, dict)
|
||||
|
||||
async def test_get_status_returns_dict(self, owncast_client: OwncastClient) -> None:
|
||||
"""get_status reports offline on a fresh server.
|
||||
|
||||
The public ``/api/status`` endpoint does not expose ``viewerCount``
|
||||
(that is only surfaced to admin callers). ``serverTime`` is always
|
||||
populated but its value is a moving target, so only its presence
|
||||
is asserted.
|
||||
"""
|
||||
status = await owncast_client.get_status()
|
||||
assert isinstance(status, dict)
|
||||
assert status["online"] is False
|
||||
assert "serverTime" in status
|
||||
|
||||
|
||||
class TestSmoke:
|
||||
"""Tests that only verify the wrapper reaches the right endpoint.
|
||||
|
||||
The backing effect is either unobservable from the integration client
|
||||
(``get_status`` does not surface the configured stream title) or concerns
|
||||
clients that are not connected in a fresh install. Assertions validate
|
||||
that the request is routed correctly, not that any effect was applied.
|
||||
"""
|
||||
|
||||
async def test_send_system_message_to_client_accepts_unknown_id(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""send_system_message_to_client returns success for unknown client IDs.
|
||||
|
||||
A fresh Owncast has no connected clients. Owncast silently accepts
|
||||
system messages to unknown client IDs (the message is discarded).
|
||||
The assertion only validates the request path reaches the server and
|
||||
the envelope parses, not that anyone received the message.
|
||||
"""
|
||||
result = await owncast_client.send_system_message_to_client(1, "hello")
|
||||
assert isinstance(result, str)
|
||||
|
||||
async def test_set_stream_title_succeeds(
|
||||
self, owncast_client: OwncastClient
|
||||
) -> None:
|
||||
"""set_stream_title returns the success envelope.
|
||||
|
||||
The /api/status endpoint does not surface streamTitle, so we only
|
||||
validate the envelope. Admin-side test covers reading the value back.
|
||||
"""
|
||||
result = await owncast_client.set_stream_title("Integration Test Stream")
|
||||
assert isinstance(result, str)
|
||||
Reference in New Issue
Block a user