Skip to content

Commit e4ea2e9

Browse files
committed
test(spyne): Added initial tests for spyne
Signed-off-by: Varsha GS <[email protected]>
1 parent de3d110 commit e4ea2e9

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed

tests/apps/spyne_app/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
import os
4+
from tests.apps.spyne_app.app import spyne_server as server
5+
from tests.apps.utils import launch_background_thread
6+
7+
app_thread = None
8+
9+
if not os.environ.get('CASSANDRA_TEST') and app_thread is None:
10+
app_thread = launch_background_thread(server.serve_forever, "Spyne")

tests/apps/spyne_app/app.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
# (c) Copyright IBM Corp. 2025
5+
6+
import logging
7+
8+
from wsgiref.simple_server import make_server
9+
from spyne import Application, rpc, ServiceBase, Iterable, UnsignedInteger, \
10+
String, Unicode, M, UnsignedInteger32
11+
12+
from spyne.protocol.json import JsonDocument
13+
from spyne.protocol.http import HttpRpc
14+
from spyne.server.wsgi import WsgiApplication
15+
16+
from spyne.error import ResourceNotFoundError
17+
18+
from tests.helpers import testenv
19+
20+
logging.basicConfig(level=logging.WARNING)
21+
logger = logging.getLogger(__name__)
22+
23+
testenv["spyne_port"] = 10818
24+
testenv["spyne_server"] = ("http://127.0.0.1:" + str(testenv["spyne_port"]))
25+
26+
class HelloWorldService(ServiceBase):
27+
# @rpc(Unicode, _returns=Unicode)
28+
# def get_resource(self, resource_id):
29+
# # Simulate checking for a resource
30+
# if resource_id != "existing_resource":
31+
# raise ResourceNotFoundError(
32+
# "Resource not found",
33+
# "The requested resource does not exist."
34+
# )
35+
# return f"Resource {resource_id} found."
36+
37+
@rpc(String, UnsignedInteger, _returns=Iterable(String))
38+
def say_hello(ctx, name, times):
39+
"""
40+
Docstrings for service methods do appear as documentation in the
41+
interface documents. <b>What fun!</b>
42+
43+
:param name: The name to say hello to
44+
:param times: The number of times to say hello
45+
46+
:returns: An array of 'Hello, <name>' strings, repeated <times> times.
47+
"""
48+
49+
for i in range(times):
50+
yield 'Hello, %s' % name
51+
52+
@rpc(_returns=Unicode)
53+
def hello(self):
54+
return "<center><h1>🐍 Hello Stan! 🦄</h1></center>"
55+
56+
@rpc(M(UnsignedInteger32))
57+
def del_user(ctx, user_id):
58+
raise ResourceNotFoundError(user_id)
59+
60+
61+
application = Application([HelloWorldService], 'spyne.examples.hello.http',
62+
in_protocol=HttpRpc(validator='soft'),
63+
out_protocol=JsonDocument(ignore_wrappers=True),
64+
)
65+
wsgi_app = WsgiApplication(application)
66+
spyne_server = make_server('127.0.0.1', testenv["spyne_port"], wsgi_app)
67+
68+
if __name__ == '__main__':
69+
# logging.info("listening to http://127.0.0.1:8000")
70+
# logging.info("wsdl is at: http://localhost:8000/?wsdl")
71+
spyne_server.request_queue_size = 20
72+
spyne_server.serve_forever()

tests/frameworks/test_spyne.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# (c) Copyright IBM Corp. 2025
2+
3+
import time
4+
import urllib3
5+
import pytest
6+
from typing import Generator
7+
8+
from tests.apps import spyne_app
9+
from tests.helpers import testenv
10+
from instana.singletons import agent, tracer
11+
from instana.span.span import get_current_span
12+
from instana.util.ids import hex_id
13+
14+
15+
class TestSpyne:
16+
@pytest.fixture(autouse=True)
17+
def _resource(self) -> Generator[None, None, None]:
18+
"""Clear all spans before a test run"""
19+
self.http = urllib3.PoolManager()
20+
self.recorder = tracer.span_processor
21+
self.recorder.clear_spans()
22+
time.sleep(0.1)
23+
24+
def test_vanilla_requests(self) -> None:
25+
response = self.http.request("GET", testenv["spyne_server"] + "/hello")
26+
spans = self.recorder.queued_spans()
27+
28+
assert len(spans) == 1
29+
assert get_current_span().is_recording() is False
30+
assert response.status == 200
31+
32+
def test_get_request(self) -> None:
33+
with tracer.start_as_current_span("test"):
34+
response = self.http.request("GET", testenv["spyne_server"] + "/hello")
35+
36+
spans = self.recorder.queued_spans()
37+
38+
assert len(spans) == 3
39+
assert get_current_span().is_recording() is False
40+
41+
spyne_span = spans[0]
42+
urllib3_span = spans[1]
43+
test_span = spans[2]
44+
45+
assert response
46+
assert 200 == response.status
47+
48+
assert "X-INSTANA-T" in response.headers
49+
assert int(response.headers["X-INSTANA-T"], 16)
50+
assert response.headers["X-INSTANA-T"] == hex_id(spyne_span.t)
51+
52+
assert "X-INSTANA-S" in response.headers
53+
assert int(response.headers["X-INSTANA-S"], 16)
54+
assert response.headers["X-INSTANA-S"] == hex_id(spyne_span.s)
55+
56+
assert "X-INSTANA-L" in response.headers
57+
assert response.headers["X-INSTANA-L"] == "1"
58+
59+
assert "Server-Timing" in response.headers
60+
server_timing_value = f"intid;desc={hex_id(spyne_span.t)}"
61+
assert response.headers["Server-Timing"] == server_timing_value
62+
63+
# Same traceId
64+
assert test_span.t == urllib3_span.t
65+
assert urllib3_span.t == spyne_span.t
66+
67+
# Parent relationships
68+
assert urllib3_span.p == test_span.s
69+
assert spyne_span.p == urllib3_span.s
70+
71+
assert spyne_span.sy is None
72+
assert urllib3_span.sy is None
73+
assert test_span.sy is None
74+
75+
# Error logging
76+
assert test_span.ec is None
77+
assert urllib3_span.ec is None
78+
assert spyne_span.ec is None
79+
80+
# wsgi
81+
assert "spyne" == spyne_span.n
82+
assert (
83+
"127.0.0.1:" + str(testenv["spyne_port"]) == spyne_span.data["http"]["host"]
84+
)
85+
assert "/hello" == spyne_span.data["http"]["url"]
86+
assert "GET" == spyne_span.data["http"]["method"]
87+
assert 200 == spyne_span.data["http"]["status"]
88+
assert spyne_span.data["http"]["error"] is None
89+
assert spyne_span.stack is None
90+
91+
def test_secret_scrubbing(self) -> None:
92+
with tracer.start_as_current_span("test"):
93+
response = self.http.request("GET", testenv["spyne_server"] + "/say_hello?name=World&times=4&secret=sshhh")
94+
95+
spans = self.recorder.queued_spans()
96+
97+
assert len(spans) == 3
98+
assert get_current_span().is_recording() is False
99+
100+
spyne_span = spans[0]
101+
urllib3_span = spans[1]
102+
test_span = spans[2]
103+
104+
assert response
105+
assert 200 == response.status
106+
107+
assert "X-INSTANA-T" in response.headers
108+
assert int(response.headers["X-INSTANA-T"], 16)
109+
assert response.headers["X-INSTANA-T"] == hex_id(spyne_span.t)
110+
111+
assert "X-INSTANA-S" in response.headers
112+
assert int(response.headers["X-INSTANA-S"], 16)
113+
assert response.headers["X-INSTANA-S"] == hex_id(spyne_span.s)
114+
115+
assert "X-INSTANA-L" in response.headers
116+
assert response.headers["X-INSTANA-L"] == "1"
117+
118+
assert "Server-Timing" in response.headers
119+
server_timing_value = f"intid;desc={hex_id(spyne_span.t)}"
120+
assert response.headers["Server-Timing"] == server_timing_value
121+
122+
# Same traceId
123+
assert test_span.t == urllib3_span.t
124+
assert urllib3_span.t == spyne_span.t
125+
126+
# Parent relationships
127+
assert urllib3_span.p == test_span.s
128+
assert spyne_span.p == urllib3_span.s
129+
130+
assert spyne_span.sy is None
131+
assert urllib3_span.sy is None
132+
assert test_span.sy is None
133+
134+
# Error logging
135+
assert test_span.ec is None
136+
assert urllib3_span.ec is None
137+
assert spyne_span.ec is None
138+
139+
# wsgi
140+
assert "spyne" == spyne_span.n
141+
assert (
142+
"127.0.0.1:" + str(testenv["spyne_port"]) == spyne_span.data["http"]["host"]
143+
)
144+
assert "/say_hello" == spyne_span.data["http"]["url"]
145+
assert spyne_span.data["http"]["params"] == "name=World&times=4&secret=<redacted>"
146+
assert "GET" == spyne_span.data["http"]["method"]
147+
assert 200 == spyne_span.data["http"]["status"]
148+
assert spyne_span.data["http"]["error"] is None
149+
assert spyne_span.stack is None

0 commit comments

Comments
 (0)