Files
ppf/tests/test_misc.py
Username 44604f1ce3 tests: add unit test infrastructure
pytest-based test suite with fixtures for database testing.
Covers misc.py utilities, dbs.py operations, and fetch.py validation.
Includes mock_network.py for future network testing.
2026-01-08 01:42:38 +01:00

249 lines
8.5 KiB
Python

# -*- coding: utf-8 -*-
"""Tests for misc.py utility functions."""
from __future__ import print_function
import re
import sys
import os
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import misc
class TestTimestamp:
"""Tests for timestamp() function."""
def test_timestamp_format(self):
"""timestamp() returns HH:MM:SS format."""
ts = misc.timestamp()
assert re.match(r'^\d{2}:\d{2}:\d{2}$', ts)
def test_timestamp_valid_hours(self):
"""timestamp() hours are 00-23."""
ts = misc.timestamp()
hours = int(ts.split(':')[0])
assert 0 <= hours <= 23
def test_timestamp_valid_minutes(self):
"""timestamp() minutes are 00-59."""
ts = misc.timestamp()
minutes = int(ts.split(':')[1])
assert 0 <= minutes <= 59
def test_timestamp_valid_seconds(self):
"""timestamp() seconds are 00-59."""
ts = misc.timestamp()
seconds = int(ts.split(':')[2])
assert 0 <= seconds <= 59
class TestTorProxyUrl:
"""Tests for tor_proxy_url() function."""
def test_basic_format(self):
"""tor_proxy_url() returns socks5:// prefix."""
result = misc.tor_proxy_url('127.0.0.1:9050')
assert result == 'socks5://127.0.0.1:9050'
def test_custom_host_port(self):
"""tor_proxy_url() works with custom host:port."""
result = misc.tor_proxy_url('10.200.1.1:9150')
assert result == 'socks5://10.200.1.1:9150'
def test_ipv6_host(self):
"""tor_proxy_url() handles IPv6 addresses."""
result = misc.tor_proxy_url('[::1]:9050')
assert result == 'socks5://[::1]:9050'
class TestLogLevel:
"""Tests for set_log_level() and get_log_level() functions."""
def setup_method(self):
"""Reset log level before each test."""
misc.set_log_level(1) # Default info level
def test_get_default_level(self):
"""get_log_level() returns default level."""
assert misc.get_log_level() == 1
def test_set_integer_level(self):
"""set_log_level() accepts integer."""
misc.set_log_level(0)
assert misc.get_log_level() == 0
misc.set_log_level(2)
assert misc.get_log_level() == 2
def test_set_string_debug(self):
"""set_log_level('debug') sets level 0."""
misc.set_log_level('debug')
assert misc.get_log_level() == 0
def test_set_string_info(self):
"""set_log_level('info') sets level 1."""
misc.set_log_level('info')
assert misc.get_log_level() == 1
def test_set_string_warn(self):
"""set_log_level('warn') sets level 2."""
misc.set_log_level('warn')
assert misc.get_log_level() == 2
def test_set_string_error(self):
"""set_log_level('error') sets level 3."""
misc.set_log_level('error')
assert misc.get_log_level() == 3
def test_set_string_none(self):
"""set_log_level('none') suppresses all."""
misc.set_log_level('none')
assert misc.get_log_level() == 99
def test_invalid_string_defaults_to_info(self):
"""set_log_level() with invalid string defaults to 1."""
misc.set_log_level('invalid')
assert misc.get_log_level() == 1
class TestIsSSLProtocolError:
"""Tests for is_ssl_protocol_error() function."""
def test_none_returns_false(self):
"""is_ssl_protocol_error(None) returns False."""
assert misc.is_ssl_protocol_error(None) is False
def test_empty_returns_false(self):
"""is_ssl_protocol_error('') returns False."""
assert misc.is_ssl_protocol_error('') is False
def test_wrong_version_number(self):
"""Detects 'wrong version number' as protocol error."""
assert misc.is_ssl_protocol_error('wrong version number') is True
def test_unsupported_protocol(self):
"""Detects 'unsupported protocol' as protocol error."""
assert misc.is_ssl_protocol_error('unsupported protocol') is True
def test_unexpected_eof(self):
"""Detects 'unexpected eof' as protocol error."""
assert misc.is_ssl_protocol_error('unexpected eof') is True
def test_eof_occurred(self):
"""Detects 'eof occurred' as protocol error."""
assert misc.is_ssl_protocol_error('eof occurred') is True
def test_alert_handshake_failure(self):
"""Detects 'alert handshake failure' as protocol error."""
assert misc.is_ssl_protocol_error('alert handshake failure') is True
def test_http_request(self):
"""Detects 'http request' as protocol error (sent HTTP to HTTPS)."""
assert misc.is_ssl_protocol_error('http request') is True
def test_no_ciphers_available(self):
"""Detects 'no ciphers available' as protocol error."""
assert misc.is_ssl_protocol_error('no ciphers available') is True
def test_case_insensitive(self):
"""is_ssl_protocol_error() is case-insensitive."""
assert misc.is_ssl_protocol_error('WRONG VERSION NUMBER') is True
assert misc.is_ssl_protocol_error('Wrong Version Number') is True
def test_certificate_error_not_protocol(self):
"""Certificate errors are not protocol errors."""
assert misc.is_ssl_protocol_error('certificate verify failed') is False
def test_hostname_mismatch_not_protocol(self):
"""Hostname mismatch is not protocol error."""
assert misc.is_ssl_protocol_error('hostname mismatch') is False
def test_expired_cert_not_protocol(self):
"""Expired certificate is not protocol error."""
assert misc.is_ssl_protocol_error('certificate has expired') is False
def test_embedded_in_message(self):
"""Detects pattern embedded in longer message."""
assert misc.is_ssl_protocol_error(
'SSL error: wrong version number in record') is True
class TestFailureConstants:
"""Tests for failure category constants."""
def test_constants_are_strings(self):
"""Failure constants are strings."""
assert isinstance(misc.FAIL_TIMEOUT, str)
assert isinstance(misc.FAIL_REFUSED, str)
assert isinstance(misc.FAIL_AUTH, str)
assert isinstance(misc.FAIL_UNREACHABLE, str)
assert isinstance(misc.FAIL_DNS, str)
assert isinstance(misc.FAIL_SSL, str)
assert isinstance(misc.FAIL_CLOSED, str)
assert isinstance(misc.FAIL_PROXY, str)
assert isinstance(misc.FAIL_OTHER, str)
def test_constants_unique(self):
"""Failure constants have unique values."""
constants = [
misc.FAIL_TIMEOUT,
misc.FAIL_REFUSED,
misc.FAIL_AUTH,
misc.FAIL_UNREACHABLE,
misc.FAIL_DNS,
misc.FAIL_SSL,
misc.FAIL_CLOSED,
misc.FAIL_PROXY,
misc.FAIL_OTHER,
]
assert len(constants) == len(set(constants))
def test_ssl_errors_contains_ssl(self):
"""SSL_ERRORS contains FAIL_SSL."""
assert misc.FAIL_SSL in misc.SSL_ERRORS
def test_conn_errors_contents(self):
"""CONN_ERRORS contains connection-related failures."""
assert misc.FAIL_TIMEOUT in misc.CONN_ERRORS
assert misc.FAIL_REFUSED in misc.CONN_ERRORS
assert misc.FAIL_UNREACHABLE in misc.CONN_ERRORS
assert misc.FAIL_CLOSED in misc.CONN_ERRORS
assert misc.FAIL_DNS in misc.CONN_ERRORS
# Auth and proxy errors are not connection errors
assert misc.FAIL_AUTH not in misc.CONN_ERRORS
assert misc.FAIL_PROXY not in misc.CONN_ERRORS
class TestLogLevels:
"""Tests for LOG_LEVELS dictionary."""
def test_debug_is_lowest(self):
"""debug is the lowest (most verbose) level."""
assert misc.LOG_LEVELS['debug'] == 0
def test_info_is_one(self):
"""info level is 1."""
assert misc.LOG_LEVELS['info'] == 1
def test_warn_is_two(self):
"""warn level is 2."""
assert misc.LOG_LEVELS['warn'] == 2
def test_error_is_three(self):
"""error level is 3."""
assert misc.LOG_LEVELS['error'] == 3
def test_none_suppresses_all(self):
"""none level (99) suppresses all output."""
assert misc.LOG_LEVELS['none'] == 99
def test_aliases_equal_info(self):
"""rate, scraper, stats, diag are aliases for info."""
assert misc.LOG_LEVELS['rate'] == misc.LOG_LEVELS['info']
assert misc.LOG_LEVELS['scraper'] == misc.LOG_LEVELS['info']
assert misc.LOG_LEVELS['stats'] == misc.LOG_LEVELS['info']
assert misc.LOG_LEVELS['diag'] == misc.LOG_LEVELS['info']