8000 test: add mockserver tests · googleapis/python-spanner-django@eff964f · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit eff964f

Browse files
committed
test: add mockserver tests
1 parent e75267a commit eff964f

File tree

10 files changed

+2781
-0
lines changed

10 files changed

+2781
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
on:
2+
push:
3+
branches:
4+
- main
5+
pull_request:
6+
name: Run Django Spanner mockserver tests
7+
jobs:
8+
mockserver-tests:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
- name: Set up Python 3.12
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.12"
18+
- name: Install nox
19+
run: python -m pip install nox
20+
- name: Run nox
21+
run: nox -s mockserver

noxfile.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"setup.py",
2525
]
2626

27+
MOCKSERVER_TEST_PYTHON_VERSION = "3.12"
2728
DEFAULT_PYTHON_VERSION = "3.8"
2829
SYSTEM_TEST_PYTHON_VERSIONS = ["3.8"]
2930
UNIT_TEST_PYTHON_VERSIONS = ["3.8", "3.9", "3.10"]
@@ -69,6 +70,7 @@ def lint_setup_py(session):
6970
def default(session, django_version="3.2"):
7071
# Install all test dependencies, then install this package in-place.
7172
session.install(
73+
"setuptools",
7274
"django~={}".format(django_version),
7375
"mock",
7476
"mock-import",
@@ -107,6 +109,32 @@ def unit(session):
107109
default(session, django_version="4.2")
108110

109111

112+
@nox.session(python=MOCKSERVER_TEST_PYTHON_VERSION)
113+
def mockserver(session):
114+
# Install all test dependencies, then install this package in-place.
115+
session.install(
116+
"setuptools",
117+
"django~=4.2",
118+
"mock",
119+
"mock-import",
120+
"pytest",
121+
"pytest-cov",
122+
"coverage",
123+
"sqlparse>=0.4.4",
124+
"google-cloud-spanner>=3.55.0",
125+
"opentelemetry-api==1.1.0",
126+
"opentelemetry-sdk==1.1.0",
127+
"opentelemetry-instrumentation==0.20b0",
128+
)
129+
session.install("-e", ".")
130+
session.run(
131+
"py.test",
132+
"--quiet",
133+
os.path.join("tests", "mockserver_tests"),
134+
*session.posargs,
135+
)
136+
137+
110138
def system_test(session, django_version="3.2"):
111139
"""Run the system test suite."""
112140
constraints_path = str(

tests/mockserver_tests/__init__.py

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2025 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from google.protobuf import empty_pb2
16+
import tests.mockserver_tests.spanner_database_admin_pb2_grpc as database_admin_grpc
17+
from google.longrunning import operations_pb2 as operations_pb2
18+
19+
20+
# An in-memory mock DatabaseAdmin server that can be used for testing.
21+
class DatabaseAdminServicer(database_admin_grpc.DatabaseAdminServicer):
22+
def __init__(self):
23+
self._requests = []
24+
25+
@property
26+
def requests(self):
27+
return self._requests
28+
29+
def clear_requests(self):
30+
self._requests = []
31+
32+
def UpdateDatabaseDdl(self, request, context):
33+
self._requests.append(request)
34+
operation = operations_pb2.Operation()
35+
operation.done = True
36+
operation.name = "projects/test-project/operations/test-operation"
37+
operation.response.Pack(empty_pb2.Empty())
38+
return operation
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Copyright 2025 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import unittest
17+
18+
from django.db import connection
19+
from google.cloud.spanner_dbapi.parsed_statement import AutocommitDmlMode
20+
import google.cloud.spanner_v1.types.type as spanner_type
21+
import google.cloud.spanner_v1.types.result_set as result_set
22+
from google.api_core.client_options import ClientOptions
23+
from google.auth.creden EF5E tials import AnonymousCredentials
24+
from google.cloud.spanner_v1 import (
25+
Client,
26+
ResultSet,
27+
PingingPool,
28+
TypeCode,
29+
)
30+
from google.cloud.spanner_v1.database import Database
31+
from google.cloud.spanner_v1.instance import Instance
32+
import grpc
33+
34+
# TODO: Replace this with the mock server in the Spanner client lib
35+
from tests.mockserver_tests.mock_spanner import (
36+
SpannerServicer,
37+
start_mock_server,
38+
)
39+
from tests.mockserver_tests.mock_database_admin import DatabaseAdminServicer
40+
41+
42+
def add_result(sql: str, result: ResultSet):
43+
MockServerTestBase.spanner_service.mock_spanner.add_result(sql, result)
44+
45+
46+
def add_update_count(
47+
sql: str,
48+
count: int,
49+
dml_mode: AutocommitDmlMode = AutocommitDmlMode.TRANSACTIONAL,
50+
):
51+
if dml_mode == AutocommitDmlMode.PARTITIONED_NON_ATOMIC:
52+
stats = dict(row_count_lower_bound=count)
53+
else:
54+
stats = dict(row_count_exact=count)
55+
result = result_set.ResultSet(dict(stats=result_set.ResultSetStats(stats)))
56+
add_result(sql, result)
57+
58+
59+
def add_select1_result():
60+
add_single_result("select 1", "c", TypeCode.INT64, [("1",)])
61+
62+
63+
def add_single_result(
64+
sql: str, column_name: str, type_code: spanner_type.TypeCode, row
65+
):
66+
result = result_set.ResultSet(
67+
dict(
68+
metadata=result_set.ResultSetMetadata(
69+
dict(
70+
row_type=spanner_type.StructType(
71+
dict(
72+
fields=[
73+
spanner_type.StructType.Field(
74+
dict(
75+
name=column_name,
76+
type=spanner_type.Type(
77+
dict(code=type_code)
78+
),
79+
)
80+
)
81+
]
82+
)
83+
)
84+
)
85+
),
86+
)
87+
)
88+
result.rows.extend(row)
89+
MockServerTestBase.spanner_service.mock_spanner.add_result(sql, result)
90+
91+
92+
def add_singer_query_result(sql: str):
93+
result = result_set.ResultSet(
94+
dict(
95+
metadata=result_set.ResultSetMetadata(
96+
dict(
97+
row_type=spanner_type.StructType(
98+
dict(
99+
fields=[
100+
spanner_type.StructType.Field(
101+
dict(
102+
name="id",
103+
type=spanner_type.Type(
104+
dict(
105+
code=spanner_type.TypeCode.INT64
106+
)
107+
),
108+
)
109+
),
110+
spanner_type.StructType.Field(
111+
dict(
112+
name="first_name",
113+
type=spanner_type.Type(
114+
dict(
115+
code=spanner_type.TypeCode.STRING
116+
)
117+
),
118+
)
119+
),
120+
spanner_type.StructType.Field(
121+
dict(
122+
name="last_name",
123+
type=spanner_type.Type(
124+
dict(
125+
code=spanner_type.TypeCode.STRING
126+
)
127+
),
128+
)
129+
),
130+
]
131+
)
132+
)
133+
)
134+
),
135+
)
136+
)
137+
result.rows.extend(
138+
[
139+
(
140+
"1",
141+
"Jane",
142+
"Doe",
143+
),
144+
(
145+
"2",
146+
"John",
147+
"Doe",
148+
),
149+
]
150+
)
151+
add_result(sql, result)
152+
153+
154+
class MockServerTestBase(unittest.TestCase):
155+
server: grpc.Server = None
156+
spanner_service: SpannerServicer = None
157+
database_admin_service: DatabaseAdminServicer = None
158+
port: int = None
159+
160+
@classmethod
161+
def setup_class(cls):
162+
os.environ["GOOGLE_CLOUD_PROJECT"] = "mockserver-project"
163+
(
164+
MockServerTestBase.server,
165+
MockServerTestBase.spanner_service,
166+
MockServerTestBase.database_admin_service,
167+
MockServerTestBase.port,
168+
) = start_mock_server()
169+
170+
@classmethod
171+
def teardown_class(cls):
172+
if MockServerTestBase.server is not None:
173+
MockServerTestBase.server.stop(grace=None)
174+
MockServerTestBase.server = None
175+
176+
def setup_method(self, test_method):
177+
self._client = None
178+
self._instance = None
179+
self._database = None
180+
connection.settings_dict["OPTIONS"]["client"] = self.client
181+
connection.settings_dict["OPTIONS"]["pool"] = PingingPool(size=10)
182+
183+
def teardown_method(self, test_method):
184+
MockServerTestBase.spanner_service.clear_requests()
185+
MockServerTestBase.database_admin_service.clear_requests()
186+
187+
@property
188+
def client(self) -> Client:
189+
if self._client is None:
190+
self._client = Client(
191+
project=os.environ["GOOGLE_CLOUD_PROJECT"],
192+
credentials=AnonymousCredentials(),
193+
client_options=ClientOptions(
194+
api_endpoint="localhost:" + str(MockServerTestBase.port),
195+
),
196+
)
197+
return self._client
198+
199+
@property
200+
def instance(self) -> Instance:
201+
if self._instance is None:
202+
self._instance = self.client.instance("test-instance")
203+
return self._instance
204+
205+
@property
206+
def database(self) -> Database:
207+
if self._database is None:
208+
self._database = self.instance.database(
209+
"test-database", pool=PingingPool(size=10)
210+
)
211+
return self._database

0 commit comments

Comments
 (0)
0