-
Notifications
You must be signed in to change notification settings - Fork 1
test: lift package line coverage 93% to 95% #764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -234,3 +234,170 @@ def test_client_configuration_fails_closed() -> None: | |
| "secret", | ||
| maximum_results=0, | ||
| ) | ||
|
|
||
|
|
||
| def test_claim_kind_classifies_project_ontology_and_plain_facts() -> None: | ||
| """The fact-kind classifier maps source conventions to claim kinds.""" | ||
| assert cv._claim_kind("project: Apollo | evidence: launch") == "semantic_project" | ||
| assert cv._claim_kind("ontology_iri: https://example.test/ontology#Project") == ( | ||
| "ontology_reference" | ||
| ) | ||
| assert cv._claim_kind("node_team A --edge_affiliation--> node_organization B") == ( | ||
| "knowledge_graph_relation" | ||
| ) | ||
| assert cv._claim_kind("plain customer-safe sentence") is None | ||
|
|
||
|
|
||
| def test_safe_external_document_rejects_malformed_and_non_http_urls() -> None: | ||
| """Only well-formed http(s), reachable documents are admissible.""" | ||
| assert cv._safe_external_document("not-a-dict") is None | ||
| assert cv._safe_external_document({}) is None | ||
| assert cv._safe_external_document({"url": " "}) is None | ||
| assert cv._safe_external_document({"url": "file:///etc/passwd"}) is None | ||
| assert cv._safe_external_document({"url": "javascript:alert(1)"}) is None | ||
| assert cv._safe_external_document({"url": ""}) is None | ||
|
|
||
|
|
||
| def test_null_claim_verification_client_raises_unavailable_runtime_error() -> None: | ||
| """An unavailable client signals the missing capability contractually.""" | ||
| client = cv.NullClaimVerificationClient() | ||
| assert client.available is False | ||
| with pytest.raises(RuntimeError, match="not configured"): | ||
| client.verify(_public_claim("Acme launch?")) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("maximum_results", [1, 2]) | ||
| def test_search_bounds_results_to_maximum(monkeypatch, maximum_results: int) -> None: | ||
| """At most ``maximum_results`` unique admissible documents are kept.""" | ||
| from lineageweave import claim_verification as cv_mod | ||
|
|
||
| def fake_search(_url, *, timeout, service_peer_name="searxng"): # noqa: ANN001 | ||
| return { | ||
| "results": [ | ||
| {"url": f"https://example.test/doc/{index}", "title": f"Doc {index}"} | ||
| for index in range(6) | ||
| ] | ||
| } | ||
|
|
||
| monkeypatch.setattr(cv_mod, "get_json", fake_search) | ||
| client = cv.SearxngOrchestratedClaimVerificationClient( | ||
| "https://searxng.test", | ||
| "https://orchestrator.test", | ||
| "synthetic-key", | ||
| maximum_results=maximum_results, | ||
| ) | ||
| documents = client._search(_public_claim("Acme launch?")) | ||
| assert len(documents) == maximum_results | ||
|
|
||
|
|
||
| def test_claim_result_to_payload_serializes_without_mixing_identifiers() -> None: | ||
| """The payload keeps external URLs separate from internal post ids.""" | ||
| result = cv.ClaimVerificationResult( | ||
| claim_text="Is Apollo at Acme?", | ||
| claim_kind="knowledge_graph_relation", | ||
| status_code=cv.CLAIM_SUPPORTED, | ||
| rationale="Public search corroborates", | ||
| source_post_ids=("11111111-1111-1111-1111-111111111111",), | ||
| evidence=( | ||
| cv.ExternalEvidenceDocument("Acme", "https://example.test/a", "snippet"), | ||
| ), | ||
| ) | ||
| payload = result.to_payload() | ||
| assert payload["claim_text"] == "Is Apollo at Acme?" | ||
| assert payload["claim_kind"] == "knowledge_graph_relation" | ||
| assert payload["status_code"] == cv.CLAIM_SUPPORTED | ||
| assert payload["source_post_ids"] == ["11111111-1111-1111-1111-111111111111"] | ||
| assert payload["evidence"][0]["url"] == "https://example.test/a" | ||
|
|
||
|
|
||
| def test_public_claim_candidates_skip_overlong_facts() -> None: | ||
| """Facts whose cleaned text exceeds 800 characters never become claims.""" | ||
| long_fact = "project: " + ("x" * 900) + " | evidence: short" | ||
| source = cv.GlobalAskSourceDocument( | ||
| post_id="11111111-1111-1111-1111-111111111111", | ||
| post_title="Public evidence", | ||
| post_body="Long fact body", | ||
| external_claim_facts=(long_fact,), | ||
| ) | ||
| assert cv.public_claim_candidates([source], "Long", maximum_claims=4) == () | ||
|
|
||
|
|
||
| def test_ontology_lookup_codes_reject_zero_budget_and_blank_question() -> None: | ||
| """A zero budget or a blank question nominates nothing.""" | ||
| assert cv.ontology_lookup_codes_for_question("anything", maximum_codes=0) == () | ||
| assert cv.ontology_lookup_codes_for_question(" ", maximum_codes=8) == () | ||
|
|
||
|
|
||
| def test_ontology_lookup_codes_match_an_explicit_ontology_iri() -> None: | ||
| """A question naming an ontology IRI nominates that entity's lookup code.""" | ||
| codes = cv.ontology_lookup_codes_for_question( | ||
| "https://contextualwisdomlab.github.io/LineageWeave/ontology#post", | ||
| maximum_codes=16, | ||
| ) | ||
| assert "node_post" in codes | ||
|
|
||
|
|
||
| def test_ontology_lookup_codes_deduplicate_like_matches() -> None: | ||
| """Repeated candidates collapse through the final deduplication.""" | ||
| codes = cv.ontology_lookup_codes_for_question( | ||
| "post post post post project project", | ||
| maximum_codes=16, | ||
| ) | ||
| assert len(codes) == len(set(codes)) | ||
|
|
||
|
|
||
| def test_search_non_list_results_return_empty() -> None: | ||
| """A malformed search body with no results list yields no evidence.""" | ||
| from lineageweave import claim_verification as cv_mod | ||
|
|
||
| def fake_search(_url, *, timeout, service_peer_name="searxng"): # noqa: ANN001 | ||
| return {"results": "not-a-list"} | ||
|
|
||
| monkeypatch = pytest.MonkeyPatch() | ||
| monkeypatch.setattr(cv_mod, "get_json", fake_search) | ||
| client = cv.SearxngOrchestratedClaimVerificationClient( | ||
| "https://searxng.test", | ||
| "https://orchestrator.test", | ||
| "synthetic-key", | ||
| maximum_results=5, | ||
| ) | ||
| assert client._search(_public_claim("Acme launch?")) == () | ||
| monkeypatch.undo() | ||
|
Comment on lines
+356
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: Manual MonkeyPatch can leak on assertion failure Two search tests build pytest.MonkeyPatch() manually and call Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+349
to
+365
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba tests/test_claim_verification.py | sed -n '349,394p'Repository: ContextualWisdomLab/LineageWeave Length of output: 210 🏁 Script executed: set -euo pipefail
printf '%s\n' '--- target lines ---'
sed -n '349,394p' tests/test_claim_verification.py
printf '%s\n' '--- pytest configuration and version references ---'
rg -n --glob '!*lock*' --glob '!*.pyc' 'pytest|pytest\.MonkeyPatch|monkeypatch' pyproject.toml pytest.ini setup.cfg tox.ini requirements*.txt tests 2>/dev/null | head -120 || true
printf '%s\n' '--- applicable repository conventions ---'
find /tmp/coderabbit-repo-knowledge/contextualwisdomlab-lineageweave-d954fb61 -maxdepth 2 -type f -name '*.md' -printRepository: ContextualWisdomLab/LineageWeave Length of output: 14686 🏁 Script executed: set -euo pipefail
printf '%s\n' '--- test conventions ---'
cat /tmp/coderabbit-repo-knowledge/contextualwisdomlab-lineageweave-d954fb61/conventions/tests.md
printf '%s\n' '--- backend test conventions ---'
cat /tmp/coderabbit-repo-knowledge/contextualwisdomlab-lineageweave-d954fb61/conventions/backend-tests.md
printf '%s\n' '--- pytest configuration ---'
sed -n '30,78p' pyproject.toml
printf '%s\n' '--- pytest import and nearby monkeypatch fixture usage ---'
sed -n '1,30p' tests/test_claim_verification.py
sed -n '1,55p' tests/test_caldav_client.pyRepository: ContextualWisdomLab/LineageWeave Length of output: 6403 🌐 Web query:
💡 Result: According to the official pytest documentation, the monkeypatch fixture automatically handles the undoing of all modifications (such as attribute, dictionary, or environment variable changes) after the requesting test function or fixture has finished [1][2][3]. There is generally no need for a user to manually call the undo method, as this is performed automatically during the teardown phase [2][4][5]. If you need to control the teardown process more granularly or restrict patches to a specific scope, you should use the context method [1][2]. The context method returns a context manager that automatically undoes any changes made within its block upon exit [2][3]. Key points from the official documentation: - Automatic Cleanup: All modifications made by the fixture are automatically reverted after the test or fixture completes [1][4]. - Manual Undo: While a monkeypatch.undo method exists, users are advised against using it directly because it is called automatically during teardown [2][5]. - Scoped Patches: For complex scenarios or to limit patches to a specific block of code, use monkeypatch.context instead of relying on the standard fixture lifecycle [1][3]. - Direct Usage: If you are using the MonkeyPatch class directly (outside of the fixture, such as in instances where the fixture is unavailable), you should either use it as a context manager (with MonkeyPatch.context as mp:) or ensure you manage the undo call explicitly [2][3]. Citations:
중복 제거 검증을 강화하고
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def test_search_deduplicates_repeated_admissible_documents() -> None: | ||
| """Duplicate URLs collapse before the maximum-result budget applies.""" | ||
| from lineageweave import claim_verification as cv_mod | ||
|
|
||
| def fake_search(_url, *, timeout, service_peer_name="searxng"): # noqa: ANN001 | ||
| return { | ||
| "results": [ | ||
| {"url": "https://example.test/a", "title": "A"}, | ||
| {"url": "https://example.test/a", "title": "A-again"}, | ||
| {"url": "https://example.test/b", "title": "B"}, | ||
| ] | ||
| } | ||
|
|
||
| monkeypatch = pytest.MonkeyPatch() | ||
| monkeypatch.setattr(cv_mod, "get_json", fake_search) | ||
| client = cv.SearxngOrchestratedClaimVerificationClient( | ||
| "https://searxng.test", | ||
| "https://orchestrator.test", | ||
| "synthetic-key", | ||
| maximum_results=5, | ||
| ) | ||
| documents = client._search(_public_claim("Acme launch?")) | ||
| assert {document.url for document in documents} == { | ||
| "https://example.test/a", | ||
| "https://example.test/b", | ||
| } | ||
| monkeypatch.undo() | ||
|
|
||
|
|
||
| def _public_claim(text: str) -> cv.PublicClaimCandidate: | ||
| """One minimal PublicClaimCandidate for client-contract tests.""" | ||
| return cv.PublicClaimCandidate( | ||
| claim_text=text, | ||
| claim_kind="knowledge_graph_relation", | ||
| source_post_ids=("11111111-1111-1111-1111-111111111111",), | ||
| ) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,183 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| """Telemetry configuration paths that require endpoint/SDK fixtures. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ``observability.configure_telemetry`` and the OTLP endpoint helpers have | ||||||||||||||||||||||||||||||||||||||||||||
| environment-gated success branches the base suite cannot exercise without | ||||||||||||||||||||||||||||||||||||||||||||
| risking provider teardown. This module monkeypatches the OpenTelemetry | ||||||||||||||||||||||||||||||||||||||||||||
| provider setters and environment so every line of the configuration and | ||||||||||||||||||||||||||||||||||||||||||||
| attribute-safety paths runs against synthetic values only. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import lineageweave.observability as observability | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _signal_endpoint(endpoint: str, signal: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||
| """Thin wrapper so callers pass one helper under test.""" | ||||||||||||||||||||||||||||||||||||||||||||
| return observability._otlp_signal_endpoint(endpoint, signal) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_signal_endpoint_appends_default_signal_suffixes() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """A bare base endpoint receives one explicit per-signal suffix.""" | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318", "metrics") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/metrics" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318", "logs") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/logs" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318", "traces") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/traces" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_signal_endpoint_preserves_an_existing_signal_suffix() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """A base that already names the signal is not suffixed twice.""" | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318/v1/metrics", "metrics") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/metrics" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318/v1/logs", "logs") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/logs" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318/v1/traces", "traces") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/traces" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| # The suffix match is case-insensitive on the trailing path. | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318/V1/METRICS", "metrics") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/V1/METRICS" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_signal_endpoint_handles_a_trailing_slash() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """Trailing slashes are stripped before appending the signal suffix.""" | ||||||||||||||||||||||||||||||||||||||||||||
| assert _signal_endpoint("http://127.0.0.1:4318/", "metrics") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/metrics" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_metric_and_log_endpoint_helpers_route_to_their_signals() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """The typed helpers select metrics and logs respectively.""" | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._otlp_metric_endpoint("http://127.0.0.1:4318") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/metrics" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._otlp_log_endpoint("http://127.0.0.1:4318") == ( | ||||||||||||||||||||||||||||||||||||||||||||
| "http://127.0.0.1:4318/v1/logs" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_safe_attributes_skips_container_values_and_unknown_keys() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """Composite and unlisted attribute values never reach a span.""" | ||||||||||||||||||||||||||||||||||||||||||||
| sanitized = observability._safe_attributes( | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| "lineageweave.operation_code": "http_post_json", | ||||||||||||||||||||||||||||||||||||||||||||
| "lineageweave.session_id": "post-123", | ||||||||||||||||||||||||||||||||||||||||||||
| "nested": {"a": 1}, | ||||||||||||||||||||||||||||||||||||||||||||
| "items": [1, 2, 3], | ||||||||||||||||||||||||||||||||||||||||||||
| "unlisted_key": "should-not-appear", | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert sanitized["lineageweave.operation_code"] == "http_post_json" | ||||||||||||||||||||||||||||||||||||||||||||
| assert sanitized["lineageweave.session_id"] == "post-123" | ||||||||||||||||||||||||||||||||||||||||||||
| assert "nested" not in sanitized | ||||||||||||||||||||||||||||||||||||||||||||
| assert "items" not in sanitized | ||||||||||||||||||||||||||||||||||||||||||||
| assert "unlisted_key" not in sanitized | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_safe_attributes_bounds_string_length_and_keeps_scalars() -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """Long strings truncate at 256 and numbers pass through unmodified.""" | ||||||||||||||||||||||||||||||||||||||||||||
| long_value = "x" * 400 | ||||||||||||||||||||||||||||||||||||||||||||
| sanitized = observability._safe_attributes( | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| "lineageweave.operation_code": long_value, | ||||||||||||||||||||||||||||||||||||||||||||
| "lineageweave.failure_outcome": "internal_error", | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| assert len(sanitized["lineageweave.operation_code"]) == 256 | ||||||||||||||||||||||||||||||||||||||||||||
| assert sanitized["lineageweave.failure_outcome"] == "internal_error" | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_configure_telemetry_success_installs_providers( | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """With an OTLP endpoint, all three signal providers are configured.""" | ||||||||||||||||||||||||||||||||||||||||||||
| import opentelemetry._logs as otel_logs | ||||||||||||||||||||||||||||||||||||||||||||
| import opentelemetry.metrics as otel_metrics | ||||||||||||||||||||||||||||||||||||||||||||
| import opentelemetry.trace as otel_trace | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| trace_providers: list[object] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| metric_providers: list[object] = [] | ||||||||||||||||||||||||||||||||||||||||||||
| log_providers: list[object] = [] | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:9") | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_CONFIGURED", False) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(otel_trace, "set_tracer_provider", trace_providers.append) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(otel_metrics, "set_meter_provider", metric_providers.append) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(otel_logs, "set_logger_provider", log_providers.append) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| observability.configure_telemetry("services/synthetic") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| assert observability._CONFIGURED is True | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._TRACE_PROVIDER is not None | ||||||||||||||||||||||||||||||||||||||||||||
| assert trace_providers == [observability._TRACE_PROVIDER] | ||||||||||||||||||||||||||||||||||||||||||||
| assert metric_providers == [observability._METER_PROVIDER] | ||||||||||||||||||||||||||||||||||||||||||||
| assert log_providers == [observability._LOG_PROVIDER] | ||||||||||||||||||||||||||||||||||||||||||||
| assert isinstance(observability._LOG_HANDLER, logging.Handler) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # Restore the module to a clean, unconfigured state for the rest of the suite. | ||||||||||||||||||||||||||||||||||||||||||||
| observability.shutdown_telemetry() | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_CONFIGURED", False) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_TRACE_PROVIDER", None) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_METER_PROVIDER", None) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_LOG_PROVIDER", None) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_LOG_HANDLER", None) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+121
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 실패 경로에서도 telemetry 상태를 정리하세요. Line 121에서 실제 provider와 logging handler를 생성합니다. Line 130의 정리는 모든 assertion 이후에만 실행됩니다. assertion이 실패하면 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_configure_telemetry_returns_when_sdk_disabled( | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """OTEL_SDK_DISABLED short-circuits without touching the providers.""" | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setenv("OTEL_SDK_DISABLED", "true") | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:9") | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_CONFIGURED", False) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| observability.configure_telemetry("services/synthetic") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| assert observability._CONFIGURED is False | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._TRACE_PROVIDER is None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_configure_telemetry_returns_without_an_endpoint( | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """An unset OTLP endpoint leaves telemetry unconfigured.""" | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_CONFIGURED", False) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| observability.configure_telemetry("services/synthetic") | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| assert observability._CONFIGURED is False | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._TRACE_PROVIDER is None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def test_shutdown_telemetry_removes_handler_and_nulls_providers( | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||||||||
| """Shutdown detaches the log handler and resets the provider globals.""" | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_TRACE_PROVIDER", object()) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_METER_PROVIDER", object()) | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_LOG_PROVIDER", object()) | ||||||||||||||||||||||||||||||||||||||||||||
| fake_handler = logging.Handler() | ||||||||||||||||||||||||||||||||||||||||||||
| monkeypatch.setattr(observability, "_LOG_HANDLER", fake_handler) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| observability.shutdown_telemetry() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| assert observability._LOG_HANDLER is None | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._TRACE_PROVIDER is None | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._METER_PROVIDER is None | ||||||||||||||||||||||||||||||||||||||||||||
| assert observability._LOG_PROVIDER is None | ||||||||||||||||||||||||||||||||||||||||||||
| assert fake_handler not in logging.getLogger().handlers | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+174
to
+183
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 실제로 연결된 logger에서 handler 제거를 검증하세요.
수정 예시 fake_handler = logging.Handler()
monkeypatch.setattr(observability, "_LOG_HANDLER", fake_handler)
+observability._LOGGER.addHandler(fake_handler)
observability.shutdown_telemetry()
-assert fake_handler not in logging.getLogger().handlers
+assert fake_handler not in observability._LOGGER.handlers📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
전체 payload를 비교하십시오.
현재 assertion은 첫 번째 evidence URL과 일부 최상위 필드만 확인합니다.
evidence에 내부 post ID가 추가되거나title또는snippet이 잘못 직렬화되어도 이 테스트는 통과합니다.payload전체를 예상 dict와 비교해서 internal ID와 external evidence의 분리 계약을 고정하십시오.수정 예시
📝 Committable suggestion
🤖 Prompt for AI Agents